How should I calculate every possible endpoint if my player can end on x movements in a turn-based game?

huangapple go评论84阅读模式
英文:

How should I calculate every possible endpoint if my player can end on x movements in a turn-based game?

问题

我目前正在Processing中重新制作一个文明游戏。我计划在游戏中实现这样一个功能:给定一个单位和允许移动的六边形数目,单位能够看到它可以进行的每一个可能移动。所有可能的终点都会用红色圆圈标记。然而,单位不能穿过山脉或水体。我试图通过找出我可以进行的每一个可能移动组合,而不让单位进入山脉或水体,但我无法确定如何确定每一个组合。

任何单位都可以朝着6个方向前进,分别是东北、北、西北、东南、南、西南。我为任何单位分配的最大移动次数可能会高达6次。如果再多,我担心每次移动单位时处理速度会变得太慢。

我正在尝试重新创建以下内容:

How should I calculate every possible endpoint if my player can end on x movements in a turn-based game?

我希望结果看起来像是有两个可能移动的情况(没有黑色箭头):

How should I calculate every possible endpoint if my player can end on x movements in a turn-based game?

该图像的原始版本:

How should I calculate every possible endpoint if my player can end on x movements in a turn-based game?

这是我用来绘制六边形网格的代码。在绘制每个单独的六边形后,它的中心x坐标和y坐标分别存储在xHexes和yHexes中。而且,在生成瓷砖类型(例如草地、沙滩)之后,瓷砖的类型也会存储在一个名为hexTypes的数组中。因此,我可以通过引用其索引来获取地图上任何我想要的六边形的x和y坐标以及类型。

绘制单个六边形的代码:

beginShape();
for (float a = PI/6; a < TWO_PI; a += TWO_PI/6) {
  float vx = x + cos(a) * gs*2;
  float vy = y + sin(a) * gs*2;
  vertex(vx, vy);
}

x是六边形中心的x坐标
y是六边形中心的y坐标
gs = 六边形的半径

用于在窗口中铺设六边形从而创建六边形网格的代码:

void redrawMap() {
  float xChange = 1.7;
  float yChange = 6;
  for (int y = 0; y < ySize/hexSize; y++) {
    for (int x = 0; x < xSize/hexSize; x++) {
      if (x % 2 == 1) {
        // 如果正在形成的六边形的任何部分在窗口上可见且不超出窗口范围。
        if (x*hexSize*xChange <= width+2*hexSize && int(y*hexSize*yChange) <= height+3*hexSize) {
          drawHex(x*hexSize*xChange, y*hexSize*yChange, hexSize);
        }
        // 仅当整个瓷砖在窗口上可见时记录并允许玩家与之交互
        if (x*hexSize*xChange < width && int(y*hexSize*yChange) < height) {
          xHexes.add(int(x*hexSize*xChange));
          yHexes.add(int(y*hexSize*yChange));
        }
      } else {
        if (x*hexSize*xChange <= width+2*hexSize && int(y*hexSize*yChange) <= height+3*hexSize) {
          drawHex(x*hexSize*xChange, y*hexSize*yChange+(hexSize*3), hexSize);
        }
        if (x*hexSize*xChange < width && int(y*hexSize*yChange+(hexSize*3)) < height) {
          xHexes.add(int(x*hexSize*xChange));
          yHexes.add(int(y*hexSize*yChange+(hexSize*3)));
        }
      }
    }
  }
}

hexSize是用户指定的每个六边形的大小,决定了屏幕上会有多少个六边形。

英文:

I'm currently recreating a Civilization game in Processing. I'm planning to implement the feature in a which a given unit can see every possible move it can make with a given number of hexes it is allowed to move. All possible endpoints are marked with red circles. However, units cannot move through mountains or bodies of water. I'm trying to approach this by finding out every possible combination of moves I can make without the unit going into a mountain or body of water but I can't figure out how I can determine every combination.

There are 6 directions that any unit can go in, north-east, north, north-west, south-east, south, south-west. The max number of movements I'm assigning to any unit would probably go up to 6. Any higher and I'm afraid processing may become to slow every time I move a unit.

I'm trying to recreate this:

How should I calculate every possible endpoint if my player can end on x movements in a turn-based game?

What I'm hoping the result will look like with two possible movements (without the black arrows):

How should I calculate every possible endpoint if my player can end on x movements in a turn-based game?

Raw version of that image:

How should I calculate every possible endpoint if my player can end on x movements in a turn-based game?

Here is the code I use to draw the hex grid. Immediately after drawing each individual hex, its center's x coords and y coords are stored in xHexes and yHexes respectively. Also, immediately after generating the type of tile (e.g. grass, beach), the type of tile is also stored in an array named hexTypes. Therefore, I can get the x and y coords and type of hex of any hex I want on the map just by referencing its index.

Code used to draw a single hexagon:

beginShape();
for (float a = PI/6; a &lt; TWO_PI; a += TWO_PI/6) {
float vx = x + cos(a) * gs*2;
float vy = y + sin(a) * gs*2;
vertex(vx, vy);
}

x is the x coord for centre of hexagon
y is the y coord for centre of hexagon
gs = radius of hexagon

Code used to tesselate hex over the window creating a hex grid:

void redrawMap() {
float xChange = 1.7;
float yChange = 6;
for (int y = 0; y &lt; ySize/hexSize; y++) {
for (int x = 0; x &lt; xSize/hexSize; x++) {
if (x % 2 == 1) {
// if any part of this hexagon being formed will be visible on the window and not off the window.
if (x*hexSize*xChange &lt;= width+2*hexSize &amp;&amp; int(y*hexSize*yChange) &lt;= height+3*hexSize) {
drawHex(x*hexSize*xChange, y*hexSize*yChange, hexSize);
}
// only record and allow player to react with it if the entire tile is visible on the window
if (x*hexSize*xChange &lt; width &amp;&amp; int(y*hexSize*yChange) &lt; height) {
xHexes.add(int(x*hexSize*xChange));
yHexes.add(int(y*hexSize*yChange));
}
} else {
if (x*hexSize*xChange &lt;= width+2*hexSize &amp;&amp; int(y*hexSize*yChange) &lt;= height+3*hexSize) {
drawHex(x*hexSize*xChange, y*hexSize*yChange+(hexSize*3), hexSize);
}
if (x*hexSize*xChange &lt; width &amp;&amp; int(y*hexSize*yChange+(hexSize*3)) &lt; height) {
xHexes.add(int(x*hexSize*xChange));
yHexes.add(int(y*hexSize*yChange+(hexSize*3)));
}
}
}
}
}

hexSize is a user-specified size for each hexagon, determining the number of hexagons that will be on the screen.

答案1

得分: 2

以下是翻译好的内容:

这个答案将帮助你理解这个问题(绿色代表平原,红色代表山丘,蓝色代表水域,同时请不要批评我糟糕的网格):

How should I calculate every possible endpoint if my player can end on x movements in a turn-based game?

请注意,这个解决方案中没有路径规划,只有一些非常简单的“我能到达那里吗”数学计算。我将在末尾包含整个代码草图,这样你就可以重现我所做的并自行测试。最后一件事:这个答案不使用任何高级设计模式,但它假设你对基础和面向对象编程感到舒适。如果我做了一些你不确定理解的事情,你可以(而且应该)问一下。

另外:这是一个概念验证,不是一个“复制粘贴”的解决方案。我没有你的代码,所以它无论如何都不会成为第二件事,但由于你的问题可以用无数种方式解决,这只是一种我故意保持简单和可视化的方式,以便你能够理解并运用它。


首先,我强烈建议你将你的瓦片变成对象。首先,因为它们需要携带大量信息(每个瓦片上都有什么,穿越的难度如何,也许还有资源或产量之类的东西……我不知道,但会有很多东西)。


基础

我将我的全局变量组织如下:

// 调试
int unitTravelPoints = 30; // 这是当前正在测试的“旅行点”数,你可以更改它

// 全局变量
float _tileSize = 60;
int _gridWidth = 10;
int _gridHeight = 20;

ArrayList<Tile> _tiles = new ArrayList<Tile>(); // 所有的瓦片
ArrayList<Tile> _canTravel = new ArrayList<Tile>(); // 当前可以旅行到的瓦片

基本上,我喜欢能够随时更改我的网格大小,但这只是一个细节。接下来要选择一个网格的坐标系统。我选择了最简单的一个,因为我不想在复杂的东西上纠结,但你可能想将其调整为另一个坐标系统。我选择了 offset 坐标 类型的网格:我的“每隔一个行”是半个瓦片的偏移。所以,不是这样:

How should I calculate every possible endpoint if my player can end on x movements in a turn-based game?

而是这样:

How should I calculate every possible endpoint if my player can end on x movements in a turn-based game?

其他的只是调整瓦片的空间坐标,使其看起来不那么糟糕,但它们的坐标保持不变:

[![有偏移但更靠近的瓦片][4]][4]

注意,我认为空间坐标和网格坐标是两个不同的东西。我大多使用空间坐标进行接近检查,但这是因为我懒惰,因为你可以创建一个漂亮的算法,不使用空间坐标执行相同的操作,而且可能成本更低。

那么关于旅行点呢?下面是我决定的工作方式:你的单位拥有有限的“旅行点”。在这里没有单位,而是一个名为 unitTravelPoints 的全局变量,它将执行相同的操作。我决定使用这个尺度:一个普通的瓦片价值10个旅行点。所以:

  1. 平原:10点
  2. 山丘:15点
  3. 水域:1000点(这是不可通过的地形,但不进一步详细说明)

我不打算详细介绍绘制网格的细节,因为在这方面你的算法看起来比我的好得多。另一方面,我将花一些时间解释我如何设计瓦片。

瓦片

我们进入面向对象编程:它们是“Drawable”。Drawable 是一个基类,包含每个可见事物应该具有的一些基本信息:位置和一个可关闭的 isVisible 设置。还有一个绘制它的方法,我称之为 Render(),因为 draw() 已经被 Processing 使用了:

class Drawable {
  PVector position;
  boolean isVisible;

  public Drawable() {
    position = new PVector(0, 0);
    isVisible = true;
  }

  public void Render() {
    // 如果你忘记了覆盖 Render() 方法,你将在控制台中看到这个错误消息
    println("Error: A Drawable just defaulted to the catch-all Render(): '" + this.getClass() + "'.");
  }
}

瓦片会更复杂一些。它将拥有更多的基本信息:行、列、是否当前选择(为什么不),像平原或山丘或水域这样的类型,一堆相邻的瓦片,绘制自身的方法和判断单位是否可以穿越它的方法:

class Tile extends Drawable {
  int row, column;
  boolean selected = false;
  TileType type;

  ArrayList<Tile> neighbors = new ArrayList<Tile>();

  Tile(int row, int column, TileType type) {
    super(); // 这调用了父类的构造函数

    this.row = row;
    this.column = column;
    this.type = type;

    // 硬编码的数字都是我用来使我的网格看起来不那么糟糕的样式,这里没有什么可看的
    position.x = (_tileSize * 

<details>
<summary>英文:</summary>

This answer will help you get to this (green is plains, red is hills and blue is water, also please don&#39;t flame my terrible grid):

[![Travel possibilities][1]][1]

Note that there is no pathfinding in this solution, only some very simple &quot;can I get there&quot; math. I&#39;ll include the full code of the sketch at the end so you can reproduce what I did and test it yourself. One last thing: this answer doesn&#39;t use any advanced design pattern, but it assume that you&#39;re confortable with the basics and Object Oriented Programming. If I did something which you&#39;re not sure you understand, you can (and should) ask about it.

Also: this is a proof of concept, not a &quot;copy and paste me&quot; solution. I don&#39;t have your code, so it cannot be that second thing anyway, but as your question can be solved in a bazillion manners, this is only one which I deliberately made as simple and visual as possible so you can get the idea and run with it.

----------

First, I strongly suggest that you make your tiles into objects. First because they need to carry a lot of information (what&#39;s on each tile, how hard they are to cross, maybe things like resources or yield... I don&#39;t know, but there will be a lot of stuff).

----------

# The Basics #

I organized my global variables like this:

    // Debug
    int unitTravelPoints = 30; // this is the number if &quot;travel points&quot; currently being tested, you can change it
    
    // Golbals
    float _tileSize = 60;
    int _gridWidth = 10;
    int _gridHeight = 20;
    
    ArrayList&lt;Tile&gt; _tiles = new ArrayList&lt;Tile&gt;(); // all the tiles
    ArrayList&lt;Tile&gt; _canTravel = new ArrayList&lt;Tile&gt;(); // tiles you can currently travel to

The basics being that I like to be able to change my grid size on the fly, but that&#39;s just a detail. What&#39;s next is to choose a coordinate system for the grid. I choose the simplest one as I didn&#39;t want to bust my brain on something complicated, but you may want to adapt this to another coordinate system. I choose the _offset coordinate_ type of grid: my &quot;every second row&quot; is half a tile offset. So, instead of having this:

[![No offset][2]][2]

I have this:

[![With offset][3]][3]

The rest is just adjusting the spatial coordinates of the tiles so it doesn&#39;t look too bad, but their coordinates stays the same:

[![With offset but the tiles closer][4]][4]

Notice how I consider that the spatial coordinates and the grid coordinates are two different things. I&#39;ll mostly use the spatial coordinates for the proximity checks, but that&#39;s because I&#39;m lazy, because you could make a nice algorithm which do the same thing without the spatial coordinates and it would probably be less costly.

What about the **travel points**? Here&#39;s how I decided to work: your unit has a finite amount of &quot;travel points&quot;. Here there&#39;s no unit, but instead a global variable `unitTravelPoints` which will do the same thing. I decided to work with this scale: one normal tile is worth 10 travel points. So:

1. Plains: 10 points
2. Hills: 15 points
3. Water: 1000 points (this is impassable terrain but without going into the details)

I&#39;m not going to go into the details of drawing a grid, but that&#39;s mostly because your algorithm looks way better than mine on this front. On the other hand, I&#39;ll spend some time on explaining how I designed the Tiles.

# The Tiles #

We&#39;re entering OOP: they are `Drawable`. `Drawable` is a base class which contains some basic info which every visible thing should have: a position, and an `isVisible` setting which can be turned off. And a method to draw it, which I call `Render()` since `draw()` is already taken by Processing:

    class Drawable {
      PVector position;
      boolean isVisible;
    
      public Drawable() {
        position = new PVector(0, 0);
        isVisible = true;
      }
    
      public void Render() {
        // If you forget to overshadow the Render() method you&#39;ll see this error message in your console
        println(&quot;Error: A Drawable just defaulted to the catch-all Render(): &#39;&quot; + this.getClass() + &quot;&#39;.&quot;);
      }
    }

The Tile will be more sophisticated. It&#39;ll have more basic informations: row, column, is it currently selected (why not), a type like plains or hills or water, a bunch of neighboring tiles, a method to draw itself and a method to know if the unit can travel through it:

    class Tile extends Drawable {
      int row, column;
      boolean selected = false;
      TileType type;
    
      ArrayList&lt;Tile&gt; neighbors = new ArrayList&lt;Tile&gt;();
    
      Tile(int row, int column, TileType type) {
        super(); // this calls the parent class&#39; constructor
    
        this.row = row;
        this.column = column;
        this.type = type;
    
        // the hardcoded numbers are all cosmetics I included to make my grid looks less awful, nothing to see here
        position.x = (_tileSize * 1.5) * (column + 1);
        position.y = (_tileSize * 0.5) * (row + 1);
        // this part checks if this is an offset row to adjust the spatial coordinates
        if (row % 2 != 0) {
          position.x += _tileSize * 0.75;
        }
      }
    
      // this method looks recursive, but isn&#39;t. It doesn&#39;t call itself, but it calls it&#39;s twin from neighbors tiles
      void FillCanTravelArrayList(int travelPoints, boolean originalTile) {
        if (travelPoints &gt;= type.travelCost) {
          // if the unit has enough travel points, we add the tile to the &quot;the unit can get there&quot; list
          if (!_canTravel.contains(this)) {
            // well, only if it&#39;s not already in the list
            _canTravel.add(this);
          }
          
          // then we check if the unit can go further
          for (Tile t : neighbors) {
            if (originalTile) {
              t.FillCanTravelArrayList(travelPoints, false);
            } else {
              t.FillCanTravelArrayList(travelPoints - type.travelCost, false);
            }
          }
        }
      }
    
      void Render() {
        if (isVisible) {
          // the type knows which colors to use, so we&#39;re letting the type draw the tile
          type.Render(this);
        }
      }
    }

# The Tile Types #

The TileType is a strange animal: it&#39;s a real class, but it&#39;s never used anywhere. That&#39;s because it&#39;s a common root for all tile types, which will inherit it&#39;s basics. The &quot;City&quot; tile may need very different variables than, say, the &quot;Desert&quot; tile. But both need to be able to draw themselves, and both need to be owned by the tiles.

    class TileType {
      // cosmetics
      color fill = color(255, 255, 255);
      color stroke = color(0);
      float strokeWeight = 2;
      // every tile has a &quot;travelCost&quot; variable, how much it cost to travel through it
      int travelCost = 10;
    
      // while I put this method here, it could have been contained in many other places
      // I just though that it made sense here
      void Render(Tile tile) {
        fill(fill);
        if (tile.selected) {
          stroke(255);
        } else {
          stroke(stroke);
        }
        strokeWeight(strokeWeight);
        DrawPolygon(tile.position.x, tile.position.y, _tileSize/2, 6);
        textAlign(CENTER, CENTER);
        fill(255);
        text(tile.column + &quot;, &quot; + tile.row, tile.position.x, tile.position.y);
      }
    }

Each tile type can be custom, now, yet each tile is... just a tile, whatever it&#39;s content. Here are the `TileType` I used in this demonstration:

    // each different tile type will adjust details like it&#39;s travel cost or fill color
    class Plains extends TileType {
      Plains() {
        this.fill = color(0, 125, 0);
        this.travelCost = 10;
      }
    }
    
    class Water extends TileType {
      // here I&#39;m adding a random variable just to show that you can custom those types with whatever you need
      int numberOfFishes = 10;
      
      Water() {
        this.fill = color(0, 0, 125);
        this.travelCost = 1000;
      }
    }
    
    class Hill extends TileType {
      Hill() {
        this.fill = color(125, 50, 50);
        this.travelCost = 15;
      }
    }

# Non-class methods #

I added a `mouseClicked()` method so we can select a hex to check how far from it the unit can travel. In your game, you would have to make it so when you select a unit all these things fall into place, but as this is just a proof of concept the unit is imaginary and it&#39;s location is wherever you click.

    void mouseClicked() {
      // clearing the array which contains tiles where the unit can travel as we&#39;re changing those
      _canTravel.clear();
    
      for (Tile t : _tiles) {
        // select the tile we&#39;re clicking on (and nothing else)
        t.selected = IsPointInRadius(t.position, new PVector(mouseX, mouseY), _tileSize/2);
        if (t.selected) {
          // if a tile is selected, check how far the imaginary unit can travel
          t.FillCanTravelArrayList(unitTravelPoints, true);
        }
      }
    }

At last, I added 2 &quot;helper methods&quot; to make things easier: 

    // checks if a point is inside a circle&#39;s radius
    boolean IsPointInRadius(PVector center, PVector point, float radius) {
      // simple math, but with a twist: I&#39;m not using the square root because it&#39;s costly
      // we don&#39;t need to know the distance between the center and the point, so there&#39;s nothing lost here
      return pow(center.x - point.x, 2) + pow(center.y - point.y, 2) &lt;= pow(radius, 2);
    }
    
    // draw a polygon (I&#39;m using it to draw hexagons, but any regular shape could be drawn)
    void DrawPolygon(float x, float y, float radius, int npoints) {
      float angle = TWO_PI / npoints;
      beginShape();
      for (float a = 0; a &lt; TWO_PI; a += angle) {
        float sx = x + cos(a) * radius;
        float sy = y + sin(a) * radius;
        vertex(sx, sy);
      }
      endShape(CLOSE);
    }

# How Travel is calculated #

Behind the scenes, that&#39;s how the program knows where the unit can travel: in this example, the unit has 30 travel points. Plains cost 10, hills cost 15. If the unit has enough points left, the tile is marked as &quot;can travel there&quot;. Every time a tile is in travel distance, we also check if the unit can get further from this tile, too.

[![Imaginary unit can go this far][5]][5]

Now, if you&#39;re still following me, you may ask: how do the tiles know which other tile is their neighbor? That&#39;s a great question. I suppose that an algorithm checking their coordinates would be the best way to handle this, but as this operation will happen only once when we create the map I decided to take the easy route and check which tiles were the close enough spatially:

    void setup() {
      // create the grid
      for (int i=0; i&lt;_gridWidth; i++) {
        for (int j=0; j&lt;_gridHeight; j++) {
          int rand = (int)random(100);
          if (rand &lt; 20) {
            _tiles.add(new Tile(j, i, new Water()));
          } else if (rand &lt; 50) {
            _tiles.add(new Tile(j, i, new Hill()));
          } else {
            _tiles.add(new Tile(j, i, new Plains()));
          }
        }
      }
    
      // detect and save neighbor tiles for every Tile
      for (Tile currentTile : _tiles) {
        for (Tile t : _tiles) {
          if (t != currentTile) {
            if (IsPointInRadius(currentTile.position, t.position, _tileSize)) {
              currentTile.neighbors.add(t);
            }
          }
        }
      }
    }

# Complete code for copy-pasting #

Here&#39;s the whole thing in one place so you can easily copy and paste it into a Processing IDE and play around with it (also, it includes how I draw my awful grid):

    // Debug
    int unitTravelPoints = 30; // this is the number if &quot;travel points&quot; currently being tested, you can change it
    
    // Golbals
    float _tileSize = 60;
    int _gridWidth = 10;
    int _gridHeight = 20;
    
    ArrayList&lt;Tile&gt; _tiles = new ArrayList&lt;Tile&gt;();
    ArrayList&lt;Tile&gt; _canTravel = new ArrayList&lt;Tile&gt;();
    
    void settings() {
      // this is how to make a window size&#39;s dynamic
      size((int)(((_gridWidth+1) * 1.5) * _tileSize), (int)(((_gridHeight+1) * 0.5) * _tileSize));
    }
    
    void setup() {
      // create the grid
      for (int i=0; i&lt;_gridWidth; i++) {
        for (int j=0; j&lt;_gridHeight; j++) {
          int rand = (int)random(100);
          if (rand &lt; 20) {
            _tiles.add(new Tile(j, i, new Water()));
          } else if (rand &lt; 50) {
            _tiles.add(new Tile(j, i, new Hill()));
          } else {
            _tiles.add(new Tile(j, i, new Plains()));
          }
        }
      }
    
      // detect and save neighbor tiles for every Tile
      for (Tile currentTile : _tiles) {
        for (Tile t : _tiles) {
          if (t != currentTile) {
            if (IsPointInRadius(currentTile.position, t.position, _tileSize)) {
              currentTile.neighbors.add(t);
            }
          }
        }
      }
    }
    
    void draw() {
      background(0);
    
      // show the tiles
      for (Tile t : _tiles) {
        t.Render();
      }
    
      // show how far you can go
      for (Tile t : _canTravel) {
        fill(0, 0, 0, 0);
        if (t.selected) {
          stroke(255);
        } else {
          stroke(0, 255, 0);
        }
        strokeWeight(5);
        DrawPolygon(t.position.x, t.position.y, _tileSize/2, 6);
      }
    }
    
    class Drawable {
      PVector position;
      boolean isVisible;
    
      public Drawable() {
        position = new PVector(0, 0);
        isVisible = true;
      }
    
      public void Render() {
        // If you forget to overshadow the Render() method you&#39;ll see this error message in your console
        println(&quot;Error: A Drawable just defaulted to the catch-all Render(): &#39;&quot; + this.getClass() + &quot;&#39;.&quot;);
      }
    }
    
    class Tile extends Drawable {
      int row, column;
      boolean selected = false;
      TileType type;
    
      ArrayList&lt;Tile&gt; neighbors = new ArrayList&lt;Tile&gt;();
    
      Tile(int row, int column, TileType type) {
        super(); // this calls the parent class&#39; constructor
    
        this.row = row;
        this.column = column;
        this.type = type;
    
        // the hardcoded numbers are all cosmetics I included to make my grid looks less awful, nothing to see here
        position.x = (_tileSize * 1.5) * (column + 1);
        position.y = (_tileSize * 0.5) * (row + 1);
        // this part checks if this is an offset row to adjust the spatial coordinates
        if (row % 2 != 0) {
          position.x += _tileSize * 0.75;
        }
      }
    
          // this method looks recursive, but isn&#39;t. It doesn&#39;t call itself, but it calls it&#39;s twin from neighbors tiles
          void FillCanTravelArrayList(int travelPoints, boolean originalTile) {
            if (travelPoints &gt;= type.travelCost) {
              // if the unit has enough travel points, we add the tile to the &quot;the unit can get there&quot; list
              if (!_canTravel.contains(this)) {
                // well, only if it&#39;s not already in the list
                _canTravel.add(this);
              }
              
              // then we check if the unit can go further
              for (Tile t : neighbors) {
                if (originalTile) {
                  t.FillCanTravelArrayList(travelPoints, false);
                } else {
                  t.FillCanTravelArrayList(travelPoints - type.travelCost, false);
                }
              }
            }
          }
    
      void Render() {
        if (isVisible) {
          // the type knows which colors to use, so we&#39;re letting the type draw the tile
          type.Render(this);
        }
      }
    }
    
    class TileType {
      // cosmetics
      color fill = color(255, 255, 255);
      color stroke = color(0);
      float strokeWeight = 2;
      // every tile has a &quot;travelCost&quot; variable, how much it cost to travel through it
      int travelCost = 10;
    
      // while I put this method here, it could have been contained in many other places
      // I just though that it made sense here
      void Render(Tile tile) {
        fill(fill);
        if (tile.selected) {
          stroke(255);
        } else {
          stroke(stroke);
        }
        strokeWeight(strokeWeight);
        DrawPolygon(tile.position.x, tile.position.y, _tileSize/2, 6);
        textAlign(CENTER, CENTER);
        fill(255);
        text(tile.column + &quot;, &quot; + tile.row, tile.position.x, tile.position.y);
      }
    }
    
    // each different tile type will adjust details like it&#39;s travel cost or fill color
    class Plains extends TileType {
      Plains() {
        this.fill = color(0, 125, 0);
        this.travelCost = 10;
      }
    }
    
    class Water extends TileType {
      // here I&#39;m adding a random variable just to show that you can custom those types with whatever you need
      int numberOfFishes = 10;
    
      Water() {
        this.fill = color(0, 0, 125);
        this.travelCost = 1000;
      }
    }
    
    class Hill extends TileType {
      Hill() {
        this.fill = color(125, 50, 50);
        this.travelCost = 15;
      }
    }
    
    
    void mouseClicked() {
      // clearing the array which contains tiles where the unit can travel as we&#39;re changing those
      _canTravel.clear();
    
      for (Tile t : _tiles) {
        // select the tile we&#39;re clicking on (and nothing else)
        t.selected = IsPointInRadius(t.position, new PVector(mouseX, mouseY), _tileSize/2);
        if (t.selected) {
          // if a tile is selected, check how far the imaginary unit can travel
          t.FillCanTravelArrayList(unitTravelPoints, true);
        }
      }
    }
    
    // checks if a point is inside a circle&#39;s radius
    boolean IsPointInRadius(PVector center, PVector point, float radius) {
      // simple math, but with a twist: I&#39;m not using the square root because it&#39;s costly
      // we don&#39;t need to know the distance between the center and the point, so there&#39;s nothing lost here
      return pow(center.x - point.x, 2) + pow(center.y - point.y, 2) &lt;= pow(radius, 2);
    }
    
    // draw a polygon (I&#39;m using it to draw hexagons, but any regular shape could be drawn)
    void DrawPolygon(float x, float y, float radius, int npoints) {
      float angle = TWO_PI / npoints;
      beginShape();
      for (float a = 0; a &lt; TWO_PI; a += angle) {
        float sx = x + cos(a) * radius;
        float sy = y + sin(a) * radius;
        vertex(sx, sy);
      }
      endShape(CLOSE);
    }

Hope it&#39;ll help. Have fun!


  [1]: https://i.stack.imgur.com/NDI9V.png
  [2]: https://i.stack.imgur.com/aVHrs.png
  [3]: https://i.stack.imgur.com/dpa1c.png
  [4]: https://i.stack.imgur.com/eME1X.png
  [5]: https://i.stack.imgur.com/r0zuI.png


</details>



# 答案2
**得分**: 1

你需要使用类似于路径规划中的算法你可以创建一个栈或队列其中存储一个类该类保存六边形的位置和从该位置出发剩余的移动次数最开始你将插入起始位置和可用移动次数并将该六边形标记为已处理以避免重复使用已经访问过的位置)。然后你弹出一个元素并插入该六边形的每个邻居移动次数减一当插入剩余移动次数为零的六边形时这些六边形就是终点在插入任何六边形之前检查它是否已经处理过

希望我表达清楚你的问题有些模糊但我尽量让你了解这些解决方案的常见做法此外我认为你的问题更适用于算法而不是处理方法

祝你好运

<details>
<summary>英文:</summary>

You will have to use similar algorithms we use on pathfinding. you create a stack or queue that will hold a class storing the position of the hex and the number of moves left from that point, initially you insert your starting position with the number of moves you have and mark that hex as done ( to not re-use a position you have already been on ), then you pop an element, and you insert every neighbor of that hex with a number of moves -1. when you insert the hexes with zero moves, those are your endpoints. And before inserting any hex check if it&#39;s not already done.

I hope I was clear, your question was a bit vague but I tried to give you an idea of how these solutions are usually done, also I think your question fits more into algorithms rather then processing

Best of luck

</details>



huangapple
  • 本文由 发表于 2020年10月12日 09:58:24
  • 转载请务必保留本文链接:https://go.coder-hub.com/64310741.html
匿名

发表评论

匿名网友

:?: :razz: :sad: :evil: :!: :smile: :oops: :grin: :eek: :shock: :???: :cool: :lol: :mad: :twisted: :roll: :wink: :idea: :arrow: :neutral: :cry: :mrgreen:

确定