12

In some strategy game I play, I'll have to change the terrain to make the water flow in some specific way to fulfill my goal. The water flow changes with terrain so if I can change the terrain wisely I can save a lot of money and time. My problem arose here------Whether can I solve this problem with Mathematica?


Terrain will be given out with a 2-D matrix, e.g. {{2,3,4},{1,2,3},{2,3,4}} present a terrain that looks like the following figure:

img1

You can visualize a terrain using the following code:

BarChart3D[{{2, 3, 4}, {1, 2, 3}, {2, 3, 4}}, ChartLayout -> "Grid", 
 Method -> {"Canvas" -> None}, ColorFunction -> "TemperatureMap", 
 ChartStyle -> Opacity@.7, ImageSize -> 300, 
 ViewPoint -> {-15, -10, 20}]

The water flow in those games follow certain simple rules to make it like real water flow(I admit that it's extremely hard to illustrate it purely by language, so I'll try to demonstrate it with some small examples. :) ):

  1. in a normal downward slope, the water keeps the height of 1. (above the terrain)

    So if a terrain can be expressed by {{2,3,4},{1,2,3},{2,3,4}} and the water's origin is at point {2,3}, the water will simply flow down with a constant height 1 along the second row like shown below and the water's height will be like {{0,0,0},{1,1,1},{0,0,0}}.

img2

  1. Water flow can create "waterfall".

    So if the terrain takes the form like {{4,5,6},{1,2,5},{4,5,6}} and the origin is at {2,3} the water's height will still be {{0,0,0},{1,1,1},{0,0,0}} and form a "waterfall" between {2,2} and {2,3}. The graphics will be like this:

img3

  1. Water will fill the gaps along it's path.

    So if a terrain can be expressed by {{2,3,4,5,6},{1,2,3,2,5},{2,3,4,5,6}} and the water's origin is at point {2,5}, the water will fill the gap at {2,4} and make it with equal height as {2,3}, but at other points on the path, water will keep its own height at 1. the resulting water height will be {{0,0,0,0,0},{1,1,1,2,1},{0,0,0,0,0}}. Check the next figure:

img4

  1. water create 'lakes'.

    if there's a gap aside the water flow, the water will rush in the gap as well and create a lake. This concept is hard to describe, so check the following image with terrain: {{6, 6, 6, 6, 6}, {6, 3, 4, 5, 6}, {6, 4, 6, 6, 6}, {5, 4, 3, 2, 1}, {6, 6, 6, 6, 6}} water's origin at {4,5} water: {{0, 0, 0, 0, 0}, {0, 2, 1, 0, 0}, {0, 1, 0, 0, 0}, {1, 1, 1, 1, 1}, {0, 0, 0, 0, 0}}:

img5


Well, finally we can get a grip of how the water flow, then we can talk about our problem:


  1. If the terrain is given, how can we solve out the water's condition?

    TestData:

    terrain={{10, 10, 10, 10, 10, 10, 10, 10}, {10, 10, 6, 6, 4, 7, 10, 10}, {10, 10, 7, 10, 5, 10, 10, 10}, {10, 10, 7, 6, 5, 10, 2, 1}, {10, 10, 8, 10, 10, 10, 10, 10}, {10, 9, 8, 6, 4, 5, 2, 1}, {10, 10, 10, 5, 10, 10, 10, 10}, {10, 6, 1, 4, 1, 1, 1, 1}, {10, 6, 6, 3, 2, 1, 1, 1}, {10, 10, 10, 10, 10, 10, 10, 10}}

    origin={6,2}

    The result should be:

    {{0, 0, 0, 0, 0, 0, 0, 0}, {0, 0, 3, 3, 5, 2, 0, 0}, {0, 0, 2, 0, 4, 0, 0, 0}, {0, 0, 2, 3, 4, 0, 0, 0}, {0, 0, 1, 0, 0, 0, 0, 0}, {0, 1, 1, 1, 2, 1, 1, 1}, {0, 0, 0, 1, 0, 0, 0, 0}, {0, 0, 4, 1, 1, 1, 1, 1}, {0, 0, 0, 1, 1, 1, 1, 1}, {0, 0, 0, 0, 0, 0, 0, 0}}


  1. If I marked some points on this map and I need them to be filled with water, how can I minimize the total changes of terrain? (1 change is +1 or -1 on one point of terrain)

This problem is quite hard and will really need sometime to read, not to say solve. So really thank everyone who actually read this post and try to solve it. Thanks!!!


I actually have one solution for the first problem, but extremely NOT ELEGANT!!! are there any way else to solve the first one?

My code is as follows:

f[terrain_, n_] := 
 Module[{list1 = 
    MorphologicalComponents[UnitStep[n - terrain], 
     CornerNeighbors -> False], 
   list2 = MorphologicalComponents[UnitStep[n + 1 - terrain], 
     CornerNeighbors -> False]}, (1 - 
      Times @@ (Unitize[list1 - #] & /@ 
         Complement[
          Flatten@Select[
             DeleteDuplicates@Thread[Flatten /@ {list1, list2}]~
              DeleteCases~{0, _}~GatherBy~(#[[2]] &)~
              Select~(Length@# > 1 &), 
             MemberQ[list2[[;; , -1]], #[[1, 2]]] && 
               Not@SubsetQ[
                 list1[[;; , -1]], #[[;; , 1]]] &][[;; , ;; , 1]], 
          list1[[;; , -1]]])) (n + 1 - terrain) + terrain]
tm = Fold[f, terrain, Range@Max@terrain - 1];
gra =
  Graph[Flatten[
    Function[{dir, list}, 
      MapIndexed[
       If[#1 == 0, {#2 -> #2 + dir, #2 + dir -> #2}, 
         If[#1 > 0, #2 -> #2 + dir, #2 + dir -> #2]] &, 
       list, {2}]] @@@ {{{1, 0}, Most@tm - Rest@tm}, {{0, 1}, 
       Transpose[Most@Transpose@tm - Rest@Transpose@tm]}}, 3], 
   VertexLabels -> "Name"];
watercal = 
 tm + SparseArray[
   VertexList@
     Reap[DepthFirstScan[gra, {6, 2}, "FrontierEdge" -> Sow]][[2, 
      1]] -> 1, Dimensions@terrain] - terrain
Wjx
  • 9,558
  • 1
  • 34
  • 70
  • I guess a random walker who walks only downwards will do for the first question. Let me see if I can code it before sleep. For the second, it sounds to me like a NP problem, I'd try to solve it with montecarlo. – tsuresuregusa Jun 11 '16 at 05:06
  • wow!but if you only move downward, how can you fill those gaps and let the water move on? – Wjx Jun 11 '16 at 05:20
  • that is my original thought, and my code consists of this part, but using only this method can cause trouble. – Wjx Jun 11 '16 at 05:21
  • you can move up till the origin level if you are not on the borders and you are stuck... and start the walker again in a modified terrain. You also need to increase the number of walkers each time there is a bifurcation, which sounds utterly inefficient. Let's see. – tsuresuregusa Jun 11 '16 at 05:33
  • oh, maybe that can solve the problem~ Let's wait and see your code :) btw, any idea on the second question? – Wjx Jun 11 '16 at 05:37
  • well, simple montecarlo would be to take a random square and change its height with a certain probability. If the total energy is lower you always accept, but if it's higher you accept with a small but non zero probability. Now the energy you would need to define is how close the water is to the marked points... don't know if you I explain myself. – tsuresuregusa Jun 11 '16 at 05:46
  • but I suppose in this system, as there're obvious differences between flowing water and static water, it's quite hard to find such a energy expression easily. Can you write a code illustrating your point? thank you very much! – Wjx Jun 12 '16 at 09:01
  • I'm still working on it, was more difficult than what I though and yesterday was the whole day busy with classes. But today I have time and at least post you the pseudo code I have. Just give me a few hours to wake up and have coffee. – tsuresuregusa Jun 12 '16 at 14:00
  • wow, thanks!!! good luck! – Wjx Jun 12 '16 at 14:40
  • sorry, I keep on thinking on different approaches to solve the problem and haven't decided for one yet. Will get back to it tomorrow, it's a really fun problem. – tsuresuregusa Jun 13 '16 at 06:26
  • Please add plots of terrain and watercal to your question and explain why watercal is the optimal solution. Thanks. – bbgodfrey Jul 11 '16 at 22:55
  • @bbgodfrey you can use the test data given. And also, the code that can generate the plot is given too. – Wjx Jul 12 '16 at 02:33
  • I have done so, but it is not obvious to me why the resulting watercal is the optimal solution. Perhaps, I am missing something. – bbgodfrey Jul 12 '16 at 03:30
  • @bbgodfrey that's not the most optimal solution, it's the ONLY solution I suppose… the water level generation should be single-solution – Wjx Jul 12 '16 at 08:40

1 Answers1

3

After a while not thinking of this problem, I think I found a better approach, though with similar method.

I modified the terrain a bit so the first row of terrain is set to 0 which symbolize the waterflow's end.

terrain = {{0, 0, 0, 0, 0, 0, 0, 0, 0, 0}, {10, 10, 10, 1, 10, 1, 10, 
    1, 1, 10}, {10, 10, 10, 2, 10, 2, 10, 1, 1, 10}, {10, 7, 10, 10, 
    10, 5, 10, 1, 1, 10}, {10, 4, 5, 5, 10, 4, 10, 1, 2, 10}, {10, 6, 
    10, 6, 10, 6, 5, 4, 3, 10}, {10, 6, 7, 7, 8, 8, 10, 1, 6, 
    10}, {10, 10, 10, 10, 10, 9, 10, 6, 6, 10}, {10, 10, 10, 10, 10, 
    10, 10, 10, 10, 10}};

t = Now;

tab = Block[{hmax = Max@terrain}, 
   terrain + Nest[Block[{mor = MorphologicalComponents[#, CornerNeighbors -> False]},
      # - Unitize[#] (1 - Unitize[mor - mor[[1, 1]]])] &, hmax - terrain, hmax]];

gra = Graph[
   Flatten[Function[{dir, list}, MapIndexed[
       If[#1 == 0, {#2 -> #2 + dir, #2 + dir -> #2}, 
         If[#1 > 0, #2 -> #2 + dir, #2 + dir -> #2]] &, 
       list, {2}]] @@@ {{{1, 0}, Most@tab - Rest@tab}, {{0, 1}, 
       Transpose[Most@Transpose@tab - Rest@Transpose@tab]}}, 3], 
   VertexLabels -> "Name"];

watercal = 
  tab + SparseArray[VertexList@Reap[DepthFirstScan[gra, {8, 6}, "FrontierEdge" -> Sow]][[2, 1]] -> 1,
   Dimensions@terrain] - terrain;

The previously very complex part is reduced to 3 simple lines in tab. And the speed is increased by approximately 2.5 times, though not much, but I believe it would be significant if the terrain matrix is larger.

This is a satisfying answer for me now. Any better method?

Wjx
  • 9,558
  • 1
  • 34
  • 70