Gravity simulator

34

6

Statement

You are given a still of a series of balls falling in a 2D grid. This grid is surrounded by immutable and unbreakable walls so all the action in contained within them. Your task is to determine what will the state of the scenario be after gravity does all it's work.

Elements inside grid

  • - Floor, doesn't alter the direction of falling balls.
  • \ Right slide, alters the path of the ball one (1) position right.
  • / Left slide, alters the path of the ball one (1) position left.
  • o A ball.

Rules

  • Balls fall.
  • Floors, and slides don't fall.
  • If the ball hits a slide that would make it go through a wall (\#or#/), or cross through a floor the slide will act as a floor.
  • When a ball hits another ball the will become one ball, but increase their power to the sum of both balls.
  • New balls (joined) will continue to behave as usual.
  • When a ball can't move anymore, it's replaced by its power.
  • The power of a ball will always be at most 9.

Input

The grid will be given in a string variable with whatever name is shortest in your language of choice. By default we will use a as the input. A sample of an input, exactly as received:

##########\n# \      #\n#o       #\n#  - -\o #\n#/-    \ #\n#  \oo-/\#\n#-/ \   /#\n#   \ \  #\n#       /#\n##########

For random grids generated use https://repl.it/B1j3/2. Use my generated page instead (no ads, no crap, just the input and the output)

Note line breaks are \n. Printing the input to screen (not required for the challenge) would show things like this. Although I've put four puzzles alongside to safe space.

##########  ##########  ##########  ##########
# \      #  # o    -/#  #       o#  #-o /    #
#o       #  #    \   #  # o     -#  #-- \ /\ #
#  - -\o #  #-  \    #  #    - \o#  # - -- o-#
#/-    \ #  #        #  #o /\    #  #/ \     #
#  \oo-/\#  #o  -o-  #  # /    -o#  #/ /o oo/#
#-/ \   /#  #   -/-  #  # -  o  -#  #o/      #
#   \ \  #  #    \\  #  #   \o  /#  #o-o    o#
#       /#  # \o\  /\#  #     \o #  # -\o o /#
##########  ##########  ##########  ##########

Output

The same grid, printed to screen with the final result of ball power. A valid answer would be one (1) of the following puzzles, each one corresponds to the input at the same position, of course if the input is different you should adjust the output. Not limit it to those four!

##########  ##########  ##########  ##########
# \      #  #      -/#  #       1#  #-1 /    #
#        #  #    \   #  #       -#  #-- \ /\ #
#1 - -\  #  #-  \    #  #    - \ #  # - --  -#
#/-    \1#  #        #  #  /\    #  #/ \     #
#  \  -/\#  #   -1-  #  # /    -2#  #/ /    /#
#-/ \   /#  #   -/-  #  # -     -#  # /      #
#   \ \  #  #    \\  #  #   \   /#  # -      #
#    2  /#  #1\2\  /\#  #2   2\1 #  #2-\3 23/#
##########  ##########  ##########  ##########

Score

Languages will compete against themselves so feel free to use nongolf languages. To validate a solution I must be able to test it somewhere to see it works!.

Score is number of bytes. In the event of a tie, the first answer to reach the tied score wins.

Warnings

  • If unsure of how a ball should react, ask me and I'll clarify, I've been as clear as I could but I'm sure there are cases that are confusing.
  • Slides are only ridden if you can exit them, think about it like a real slide. There's a guy at the top that doesn't let you through the ball unless it will exit through the other side.

Clarifying examples of ball movement

######                       ######
#-o- #    BALL WOULD GO RD   #- - #
# \  #                       # \o #
######                       ######

######                       ######
#-o- #     BALL WOULD STAY   #-o- #
# \\ #                       # \\ #
######                       ######

######                       ######
#  -o#     BALL WOULD STAY   #  -o#
#   \#                       #   \#
######                       ######

######                       ######
#  o #     BALL WOULD STAY   #  o #
#  \/#                       #  \/#
######                       ######

######                       ######
#-o- #    BALL WOULD GO LD   #- - #
# /\ #                       #o/\ #
######                       ######

UPDATES

How can I test if my answer is valid?

I've set up a simple page in one of my sites that will give you a random puzzle, and its answer. Take the input and check it against the output. My solution, without worrying too much about golfing is 389b 355b in python (generator, and page also python)

Leaderboard

var QUESTION_ID=64359,OVERRIDE_USER=8478;function answersUrl(e){return"https://api.stackexchange.com/2.2/questions/"+QUESTION_ID+"/answers?page="+e+"&pagesize=100&order=desc&sort=creation&site=codegolf&filter="+ANSWER_FILTER}function commentUrl(e,s){return"https://api.stackexchange.com/2.2/answers/"+s.join(";")+"/comments?page="+e+"&pagesize=100&order=desc&sort=creation&site=codegolf&filter="+COMMENT_FILTER}function getAnswers(){jQuery.ajax({url:answersUrl(answer_page++),method:"get",dataType:"jsonp",crossDomain:!0,success:function(e){answers.push.apply(answers,e.items),answers_hash=[],answer_ids=[],e.items.forEach(function(e){e.comments=[];var s=+e.share_link.match(/\d+/);answer_ids.push(s),answers_hash[s]=e}),e.has_more||(more_answers=!1),comment_page=1,getComments()}})}function getComments(){jQuery.ajax({url:commentUrl(comment_page++,answer_ids),method:"get",dataType:"jsonp",crossDomain:!0,success:function(e){e.items.forEach(function(e){e.owner.user_id===OVERRIDE_USER&&answers_hash[e.post_id].comments.push(e)}),e.has_more?getComments():more_answers?getAnswers():process()}})}function getAuthorName(e){return e.owner.display_name}function process(){var e=[];answers.forEach(function(s){var r=s.body;s.comments.forEach(function(e){OVERRIDE_REG.test(e.body)&&(r="<h1>"+e.body.replace(OVERRIDE_REG,"")+"</h1>")});var a=r.match(SCORE_REG);a&&e.push({user:getAuthorName(s),size:+a[2],language:a[1],link:s.share_link})}),e.sort(function(e,s){var r=e.size,a=s.size;return r-a});var s={},r=1,a=null,n=1;e.forEach(function(e){e.size!=a&&(n=r),a=e.size,++r;var t=jQuery("#answer-template").html();t=t.replace("{{PLACE}}",n+".").replace("{{NAME}}",e.user).replace("{{LANGUAGE}}",e.language).replace("{{SIZE}}",e.size).replace("{{LINK}}",e.link),t=jQuery(t),jQuery("#answers").append(t);var o=e.language;/<a/.test(o)&&(o=jQuery(o).text()),s[o]=s[o]||{lang:e.language,user:e.user,size:e.size,link:e.link}});var t=[];for(var o in s)s.hasOwnProperty(o)&&t.push(s[o]);t.sort(function(e,s){return e.lang>s.lang?1:e.lang<s.lang?-1:0});for(var c=0;c<t.length;++c){var i=jQuery("#language-template").html(),o=t[c];i=i.replace("{{LANGUAGE}}",o.lang).replace("{{NAME}}",o.user).replace("{{SIZE}}",o.size).replace("{{LINK}}",o.link),i=jQuery(i),jQuery("#languages").append(i)}}var ANSWER_FILTER="!t)IWYnsLAZle2tQ3KqrVveCRJfxcRLe",COMMENT_FILTER="!)Q2B_A2kjfAiU78X(md6BoYk",answers=[],answers_hash,answer_ids,answer_page=1,more_answers=!0,comment_page;getAnswers();var SCORE_REG=/<h\d>\s*([^\n,]*[^\s,]),.*?(\d+)(?=[^\n\d<>]*(?:<(?:s>[^\n<>]*<\/s>|[^\n<>]+>)[^\n\d<>]*)*<\/h\d>)/,OVERRIDE_REG=/^Override\s*header:\s*/i;
body{text-align:left!important}#answer-list,#language-list{padding:10px;width:290px;float:left}table thead{font-weight:700}table td{padding:5px}
<script src="https://ajax.googleapis.com/ajax/libs/jquery/2.1.1/jquery.min.js"></script> <link rel="stylesheet" type="text/css" href="//cdn.sstatic.net/codegolf/all.css?v=83c949450c8b"> <div id="answer-list"> <h2>Leaderboard</h2> <table class="answer-list"> <thead> <tr><td></td><td>Author</td><td>Language</td><td>Size</td></tr></thead> <tbody id="answers"> </tbody> </table> </div><div id="language-list"> <h2>Winners by Language</h2> <table class="language-list"> <thead> <tr><td>Language</td><td>User</td><td>Score</td></tr></thead> <tbody id="languages"> </tbody> </table> </div><table style="display: none"> <tbody id="answer-template"> <tr><td>{{PLACE}}</td><td>{{NAME}}</td><td>{{LANGUAGE}}</td><td>{{SIZE}}</td><td><a href="{{LINK}}">Link</a></td></tr></tbody> </table> <table style="display: none"> <tbody id="language-template"> <tr><td>{{LANGUAGE}}</td><td>{{NAME}}</td><td>{{SIZE}}</td><td><a href="{{LINK}}">Link</a></td></tr></tbody> </table>

Juan Cortés

Posted 2015-11-19T22:56:23.043

Reputation: 1 356

1

I'm reminded of Marbelous.

– Arcturus – 2015-11-20T02:08:44.497

10Bonus points if anybody answers in Marbelous. – Mego – 2015-11-20T02:09:50.330

Borderline dupe of A Mere Bagatelle and Not Your Routine Bean Machine

– Peter Taylor – 2015-11-20T08:20:25.200

sounds like potentially an ascii-art pinball game – Khaled.K – 2015-12-09T22:28:45.083

@JuanCortés why don't you use the fancy leader board code so you don't have to update the rankings yourself? – usandfriends – 2016-01-04T16:56:33.240

Didn't know it existed, but got it :) – Juan Cortés – 2016-01-04T17:13:20.913

Should we really care about a ball having power > 9 ? For instance @usandfriends solution does not handle that case – edc65 – 2016-01-04T19:49:39.327

For this first challenge no, it's a very edge case that might be plausible but no. Max 9 – Juan Cortés – 2016-01-04T20:34:28.190

@JuanCortés I'm thinking of starting a 1000 point bounty for a Marbelous solution. Can you discuss in chat to iron out the details first? – lirtosiast – 2016-01-04T21:42:10.040

I feel like this question is a dupe – Rohan Jhunjhunwala – 2016-07-09T11:00:19.810

I'm sorry you feel that way – Juan Cortés – 2016-07-09T12:20:22.527

What about " o\n_/"? – Titus – 2017-11-08T01:56:45.083

Answers

6

JavaScript (ES6), 157 196

Edit char by char instead of row by row, much better result

g=>(s=~g.search`
`,g=[...g]).map((c,i)=>c<' '?0:g[[0,1,-1].map(d=>!d|'\\ /'[d+1]==g[d+=i]&&+g[d+=s]?g[v+=+g[d],d+v-v]=' ':0,v=c>'a'?1:+c),i]=v?v:c)&&g.join``

Note: does not handle ball values > 9. But it could, with a 18 bytes cost. See basic code below.

TEST snippet (better full page)

F=g=>(s=~g.search`
`,g=[...g]).map((c,i)=>c<' '?0:g[[0,1,-1].map(d=>!d|'\\ /'[d+1]==g[d+=i]&&+g[d+=s]?g[v+=+g[d],d+v-v]=' ':0,v=c=='o'?1:+c),i]=v?v:c)&&g.join``

// Basic code, starting point before golfing
B=g=>{
  s = ~g.search('\n');
  (g=[...g]).map((c,i)=>{
    v = c == 'o' ? 1 : +c
    if (c>=' ' // skip newlines
        && !isNaN(v)) // digit or space
    {
      if (w=+g[i+s]) v += w, g[i+s]=' '
      if (g[i-1]=='\\' && (w=+g[i+s-1])) v += w, g[i+s-1]=' '
      if (g[i+1]=='/' && (w=+g[i+s+1])) v += w, g[i+s+1]=' '
      if (v) g[i] = v
    }
  })      
  // return g.join``
  // To handle values > 9 ...
  return g.map(v=>+v?v%10:v).join``
}  

function test() {
  O.textContent = F(I.value)
}

test()
textarea,pre { width: 15em; height: 15em; display: block; margin: 0; }
iframe { height: 25em; width: 15em}
td { vertical-align: top }
<table>
  <tr>
    <th>Test cases</th>
    <th>Input</th>
    <td></td>
    <th>Output</th>
  </tr><tr>
    <td>
    Copy/paste test cases from here <br>(courtesy of OP)
    <button onclick="T.src='http://bohem.io/wadus/index.php'">reload</button><br>
    <iframe id=T src="http://bohem.io/wadus/index.php"></iframe>
    </td>
    <td><textarea id=I>##########
#  o  o o#
# o\o o  #
#oo o/   #
#       o#
#     /o #
#\o   o  #
# o /-   #
#   o  - #
##########</textarea></td>
    <td><button onclick='test()'>Test</button></td>
    <td><pre id=O></pre></td>
  </tr>
</table>

edc65

Posted 2015-11-19T22:56:23.043

Reputation: 31 086

Nice! I have a lot to learn in the ways of golf – usandfriends – 2016-01-04T23:48:59.807

shouldn´t you map values >9 with v>9?9:v? – Titus – 2017-11-08T02:29:37.577

@Titus I could, but in fact I could do whatever I want, as a value > 9 is not expected, see the comment by OP answering my question. – edc65 – 2017-11-08T10:00:03.930

5

Javascript (ES6), 453 426 409 306 290 286 bytes

The first and most obvious solution that came to my mind is one that looks around the slides and then merges or replaces.

a=>{a=a.split`
`.map(b=>[...b.replace(/o/g,'1')]);for(r=1;r<a.length-1;r++){d=a[r];for(c=1;c<d.length-1;c++){e=a[r+1];f=e[c]=='\\'?c+1:e[c]=='/'?c-1:!isNaN(+e[c])?c:null;(''+d[c]).match(/[0-9]/g)&&f!=null&&!isNaN(+e[f])?(e[f]=+e[f]+ +d[c],d[c]=' '):0}}return a.map(b=>b.join``).join`
`}

Ungolfed:

func = state => {
    state = state.split `
`.map(line => [...line.replace(/o/g, '1')]);

    for (r = 1; r < state.length - 1; r++) {
        thisState = state[r];
        for (c = 1; c < thisState.length - 1; c++) {
            nextState = state[r + 1];
            nc = nextState[c] == '\\' ? c + 1 : nextState[c] == '/' ? c - 1 : !isNaN(+nextState[c]) ? c : null;

            ('' + thisState[c]).match(/[0-9]/g) && nc != null && !isNaN(+nextState[nc]) ? (
                nextState[nc] = +nextState[nc] + +thisState[c],
                thisState[c] = ' '
            ) : 0;
        }
    }

    return state.map(line => line.join ``).join `
`;
}

Test like:

func(`##########
# -- o - #
# \\\\\\ -  #
#-       #
# o  o   #
#o \\\\ /-\\#
#      \\ #
#/-  //  #
#   /- o #
##########`)

Thanks to: @edc65

usandfriends

Posted 2015-11-19T22:56:23.043

Reputation: 687

I'll post my python when I'm sure I can't golf it anymore, but so far it's the python code that's generating the answer. Any way I can test your golfed code somewhere so I can pop you on the leaderboard? (jsfiddle,jsbin,ideone,whatever) – Juan Cortés – 2016-01-01T21:01:01.417

got it down to 355, your move! – Juan Cortés – 2016-01-04T02:54:20.357

@JuanCortés Done! – usandfriends – 2016-01-04T16:44:09.730

b.replace(/o/g,'1').split`` can be shortened to [...b.replace(/o/g,1)] – edc65 – 2016-01-04T19:44:59.087

@edc65 I think I fixed it. Basically it always keeps the power under 10 by mod'ing 10. – usandfriends – 2016-01-04T20:22:25.070

@edc65 Okay I can remove the mod then and save a few bytes. – usandfriends – 2016-01-04T21:52:49.337

4

Java, Too Many 1102 987 bytes

Because, Java.

\o/ It's under 1000!

class G{class T{char s;int p=0;T(char c){s=c;}}T A=new T(' ');T[][]o;boolean i(){for(int i=1;i<o.length;i++)for(int j=1;j<o[i].length;j++)if(o[i][j].p>0){if(m(i,j,i+1,j)||o[i+1][j].s=='/'&&m(i,j,i+1,j-1)||o[i+1][j].s=='\\'&&m(i,j,i+1,j+1))return 1>0;int w=o[i][j].p;o[i][j]=new T(Integer.toString(w).charAt(0)){{p=w;}};}return 1<0;}boolean m(int a,int b,int c,int d){if(o[c][d]==A||o[c][d].p>0){o[a][b].p+=o[c][d].p;o[c][d]=o[a][b];o[a][b]=A;return 1>0;}return 1<0;}String s(){String s="";for(T[]r:o){for(T t:r)s+=t.s;s+="\n";}return s;}void f(String s){String[]r=s.split("\\\\n");o=new T[r.length][r[0].length()];for(int i=0;i<o.length;i++)for(int j=0;j<o[i].length;j++)switch(r[i].charAt(j)){case'-':o[i][j]=new T('-');break;case'\\':o[i][j]=new T('\\');break;case'/':o[i][j]=new T('/');break;case'o':o[i][j]=new T('o'){{p=1;}};break;case'#':o[i][j]=new T('#');break;default:o[i][j]=A;}}public static void main(String[]a){G g=new G();g.f(a[0]);while(g.i());System.out.println(g.s());}}

A side goal was being able to print every iteration of the board: just remove the middle ; in while(g.i()) ; System.out.print(g.s()); (Though this does disable the last print which has the 0->power conversion). Unfortunately, in this version, gravity works in strangely. Each pass I take the first non-stuck ball and move it. Short circuiting iterate() there is less bytes than going over the whole board then returning if anything changed.

This is a complete main class, compile and run on the command line with argument:

java -jar G.jar "##########\n# o-/    #\n#-  / -/ #\n# oo   o #\n# /   \o #\n# o   o \#\n#    o   #\n#   -\o  #\n#\  \\ o/#\n##########"

"Readable" version:

class GravitySimulator {
    class Token {
        char symbol;
        int power = 0;

        Token(char c) {
            symbol = c;
        }
    }

    Token A = new Token(' ');

    Token[][] board;

    boolean iterate() {
        for (int i=1; i<board.length; i++)
            for (int j=1; j<board[i].length; j++) 
                if (board[i][j].power>0) {
                    if (move(i,j,i+1,j) || board[i+1][j].symbol=='/' && move(i,j,i+1,j-1) || board[i+1][j].symbol=='\\' && move(i,j,i+1,j+1)) return true;
                    int pow = board[i][j].power;
                    board[i][j] = new Token(Integer.toString(pow).charAt(0)){{power=pow;}};
                }
        return false;
    }

    boolean move(int x1, int y1, int x2, int y2) {
        if (board[x2][y2] == A || board[x2][y2].power>0) {
            board[x1][y1].power += board[x2][y2].power;
            board[x2][y2] = board[x1][y1];
            board[x1][y1] = A;
            return true;
        } return false;
    }

    String string() {
        String s = "";
        for (Token[] row : board) {
            for (Token token : row) s+=token.symbol;
            s+="\n";
        }
        return s;
    }

    void fromString(String s) {
        String[] rows = s.split("\\\\n");
        board = new Token[rows.length][rows[0].length()];
        for (int i=0; i<board.length; i++) 
            for (int j=0; j<board[i].length; j++) 
                switch(rows[i].charAt(j)) {
                    case '-': board[i][j]=new Token('-');break;
                    case '\\':board[i][j]=new Token('\\');break;
                    case '/': board[i][j]=new Token('/');break;
                    case 'o': board[i][j]=new Token('o'){{power=1;}};break;
                    case '#': board[i][j]=new Token('#');break;
                    default:  board[i][j]=A;
                }
    }

    public static void main(String[] args) {
        GravitySimulator g = new GravitySimulator();
        g.fromString(args[0]);
        while(g.iterate());
        System.out.println(g.string());
    }
}

CAD97

Posted 2015-11-19T22:56:23.043

Reputation: 1 367

Such java much verbose. +1 – Rohan Jhunjhunwala – 2016-07-09T11:01:47.583

1

Python3, 355b

g=g.replace("o","1").split("\n")
r=1
while r:
 r=0
 for y in range(len(g)):
  for x in range(len(g[y])):
   if g[y][x].isdigit():
    h=g[y+1]
    m={"/":-1,"\\":1}
    j=x+m[h[x]]if h[x]in m else x
    if("0"+h[j].strip()).isdigit():
     r=1
     g[y+1]=h[:j]+str(int(g[y][x])+int("0"+h[j]))+h[j+1:]
     g[y]=g[y][:x]+' '+g[y][x+1:]
print("\n".join(g))

Test here

Juan Cortés

Posted 2015-11-19T22:56:23.043

Reputation: 1 356

0

PHP, 228 204 197 194 bytes

for($a=strtr($a,o,1);$c=$a[$i];$i++)$c>0&&(($d=$a[$t=$i+strpos($a,"
")+1])>" "?$d!="/"?$d!="\\"?$d>0:$a[++$t]<"!"||$a[$t]>0:$a[--$t]<"!"||$a[$t]>0:1)&&$a[$t]=min($a[$t]+$c,9).!$a[$i]=" ";echo$a;

yields warnings in PHP 7.1. Insert (int) before $a[$t]+$c to fix.

Run with php -nr '$a="<string>";<code>' or try it online.

breakdown

for($a=strtr($a,o,1);   # replace "o" with "1"
    $c=$a[$i];$i++)     # loop through string
    $c>0                    # if character is numeric
    &&(($d=$a[                  # and ...
        $t=$i+                  # 3: target position = current position + 1 line
            strpos($a,"\n")+1   # 2: width = (0-indexed) position of first newline +1
    ])>" "                  # if target char is not space
        ?$d!="/"                # and not left slide
        ?$d!="\\"               # and not right slide
        ?$d>0                   # but numeric: go
        :$a[++$t]<"!"||$a[$t]>0     # right slide: if target+1 is space or ball, then go
        :$a[--$t]<"!"||$a[$t]>0     # left slide: if target-1 is space or ball, then go
    :1                              # space: go
    )&&                     # if go:
        $a[$t]=min($a[$t]+$c,9) # move ball/merge balls
        .!$a[$i]=" "            # clear source position
    ;
echo$a;                 # print string

Titus

Posted 2015-11-19T22:56:23.043

Reputation: 13 814