Дивіться, як вони падають, як доміно


22

Ви живете в терміналі, що має 80 символів. Вам нудно, тому ви вирішили пограти в доміно. Ні, не нудні види, схожі на Scrabble, забава, де ви проводите годину, налаштовуючи їх, щоб спостерігати, як вони за секунду падають.

У терміналах доміно виглядає так:

|   upright domino
\   left-tilted domino
/   right-tilted domino
__  fallen domino

Як ми всі знаємо, якщо нахилене доміно торкається вертикального, друге доміно також нахиляється. Єдиний виняток з цього - якщо два нахилених доміно торкаються його:

|\ --> \\        /| --> //        /|\ --> /|\

Відрегулюйте гравітаційну константу вашого терміналу, щоб цей перехід займав 100 мс.

Якщо нахилене доміно підтримується іншим доміно чи стінами терміналу, його подорож закінчується.

Жодне з нахилених доміно в Росії

\||||____||||/__                /|\    /\    /|\                __\||||____||||/

(80 символів) переміститься, оскільки два найвищих нахилених доміно підтримуються стінками терміналу, а всі інші підтримуються іншими доміно.

Однак якщо простір у напрямку нахилу порожній, доміно падає:

| \\ --> |__\        // | --> /__|

Термінал. Гравітаційна константа. Ви отримуєте бал ...

Нарешті, зліва злегка вітер, тому правонахилені доміно падають швидше, ніж нахили ліворуч:

|/ \| --> |__\|

Завдання

Напишіть програму / функцію, яка показує анімацію гри в доміно в терміналі.

Ваш код повинен робити наступне:

  1. Прочитайте рядок із вхідних даних, що представляє початковий стан доміно.

    Цей рядок міститиме не більше 80 символів і складається виключно з описаних вище доміно та порожніх пробілів.

  2. Роздрукуйте стан та чекайте 100 мс.

  3. Перетворіть стан, як пояснено вище.

  4. Якщо стан змінився, поверніться до 2.

Додаткові правила

  • Довжина вхідного рядка не впливає на ширину терміналу; навіть якщо рядок коротший, ніж 80 символів, стінки терміналу все ще знаходяться на відстані 80 символів.

  • Кожен раз, коли виконується крок 2, стан повинен надрукуватись у тому самому місці, замінюючи попередній стан.

  • Оскільки деякі мови не здатні чекати рівно 100 мс, сміливо чекайте будь-якої кількості між 50 і 1000 мс.

  • Діють стандартні правила .

Приклади

  • Для початкового стану

     ||\/||
    

    надрукуйте наступне (один над іншим):

     ||\/||
     |\\//|
     \\\///
    __\\//__
    
  • Для початкового стану

    /||||\
    

    надрукувати наступне

    /||||\
    //||\\
    ///\\\
    
  • Для початкового стану

    /|||\
    

    надрукувати наступне

    /|||\
    //|\\
    
  • Для початкового стану

    |/ \|/ \|/ \|/ \|
    

    надрукувати наступне:

    |__\|__\|__\|__\|
    
  • Для початкового стану (80 символів)

    \||||____||||/__                /|\    /\    /|\                __\||||____||||/
    

    надрукувати наступне

    \||||____||||/__                /|\    /\    /|\                __\||||____||||/
    

Відповіді:


13

Сітківка , 87 86 85 байт

Завдяки Деннісу за збереження 1 байта.

^.{0,79}$
$0 
:`^
<ESC>c
(`/ | \\
__
/\|(?!\\)
//a
(?<!/)\|\\
\\
$
aaaaa
a
aaaa
(a+)+b|a
<empty>

<ESC>слід замінити фактичним символом управління (0x1B). <empty>являє собою порожню кінцеву лінію. Потім можна запустити наведений вище код з одного файлу з -sпрапором.

Для коду потрібен термінал, який підтримує коди аварійних відхилень ANSI. Я не можу придушити подачу ліній у виході Retina, тому мені потрібно очищати всю консоль <ESC>cкожного разу. Я перевіряв код у баші, використовуючи Mono для запуску Retina.

Пояснення

^.{0,79}$
$0 

Почнемо з додавання пробілу, якщо вхід містить менше 80 символів. Це так, що /на правильному кінці не потрібно обробляти окремо.

:`^
<ESC>c

Тепер ми додаємо <ESC>cдо рядка, який є кодом виходу ANSI для очищення терміналу. Тому щоразу, коли рядок буде надруковано, це буде робити це у верхній частині терміналу. :`Інструктує Retina роздрукувати результат цієї підстановки, тобто початкової конфігурації.

(`/ | \\
__

(`починається петля. Оскільки немає відповідності ), цикл передбачається пройти до останнього етапу програми. Кожна ітерація буде імітувати один крок падіння доміно, а потім трохи "спати". Цей перший етап замінює, /а \поруч із пробілом у __. Це автоматично обробляє / \справу правильно, оскільки збіги не можуть перетинатися і шукати зліва направо. Тож /<sp>відповідність перетворилася б на __таку, що \неможливо зіставити, і ми отримаємо правильне __\.

/\|(?!\\)
//a

Це перетворюється /|на //умови, що поруч немає \. Ми додаємо aтаке, що це нове /не возиться з наступним етапом (який не повинен "знати" про цю зміну ще).

(?<!/)\|\\
\\

Протилежна ситуація: перетворіться |\за \\умови, що поруч немає /. Нам тут не потрібно ставити a, тому що ми закінчили цей крок моделювання.

Тепер спляча частина ...

$
aaaaa

Додає ще 5 as до кінця коду.

a
aaaa

Перетворюється кожен aна 4 aс, тому aв кінці отримуємо 20 с.

(a+)+b|a
<empty>

Тепер весела частина ... ми трохи спимо за допомогою катастрофічного зворотного треку . Існує експоненціальна кількість способів розділити відповідність (a+)+між повтореннями групи. Оскільки bпричини збігу не вдається, двигун відхилиться і спробує кожну з цих комбінацій, перш ніж вирішити, що (a+)+bне може збігатися. За двадцять aс в кінці, що займає щось на зразок півсекунди.

У той же час, ми дозволяємо, щоб регулярний вирівнювання відповідав синглу a, але тільки після виконання зворотного трекінгу. Коли це відповідає, ми замінюємо його порожнім рядком, видаляючи всі рядки, aякі ми вставили з тієї чи іншої причини з рядка.

Це залишає друк рядка в кінці циклу ітерації. Ось корисно, що я ще не виправив поведінку друку петель у Retina. Наразі існує лише один прапор для кожного етапу, на якому написано "надрукувати" або "не друкувати". За замовчуванням "не друкувати" за винятком останнього етапу програми, який за замовчуванням "друкує". Але етап циклічно, так що це означає, що він фактично друкує поточний рядок на кожній ітерації. Зазвичай це насправді дратує, і вам майже завжди потрібно включити додатковий порожній етап в кінці, якщо ви хочете лише остаточний результат, але тут він дозволяє мені зберегти чотири байти.


6

Javascript (ES6), 206 148 129 158 байт

Нарешті я звів її до дуже низької точки, але це не очистило б консоль і не додало зайвого місця; ці проблеми вже виправлені.

c=console;d=s=>{c.clear(s[79]||(s+=' ')),c.log(s),t=s[R='replace'](/\/ | \\/g,'__')[R](/\/\|/g,'//a')[R](/\|\\/g,'\\\\')[R](/a/g,'');t!=s&&setTimeout(d,99,t)}

Альтернативна версія 153 байтів, яка повинна працювати в Node.JS:

d=s=>{s[79]||(s+=' '),console.log("\033c"+s),t=s[R='replace'](/\/ | \\/g,'__')[R](/\/\|/g,'//a')[R](/\|\\/g,'\\\\')[R](/a/g,'');t!=s&&setTimeout(d,99,t)}

ІМХО, з нею дуже весело грати. Спробуйте HTML-версію тут:

Напевно, є трохи більше місця для гольфу. Пропозиції Ласкаво просимо!


+1 для демо-версії, яка витрачається на 10 хвилин мого часу, і +1 для функції рандомизатора. Однак, як зазначає Денніс, це не вдається для першого тестового випадку. Спробуйте /або, /|і ви побачите, що плитка не впаде так, як слід.
dberm22

@ Денніс Дякуємо за вказівку на ці проблеми. Я вважаю, що я виправив їх обох зараз.
ETHproductions

Вузол не задоволений жировою стрілкою, але в іншому випадку вона добре працює. Ви можете замінити \033буквальний байт ESC, заощадивши 3 байти.
Денніс

2

Перл 5, 154 146

Довелося використовувати тимчасовий символ для підтримання стану між двома регулярними виразами.
Впоратися з ризиком того, що щось на зразок / | | | \ закінчується як / / / \ \ замість / / | \ \.

$_=substr(pop.' ',0,80);$|++;while($}ne$_){print"$_\r";$}=$_;s@ \\|/ @__@g;s@/\|(?=[^\\])@/F@g;s@([^/])\|\\@$1\\\\@g;tr@F@/@;select($\,$\,$\,0.1)}

Тест

$ perl dominos.pl '|\ |\/|||\/|'
|\__\//|\\/__

1
Ви можете усунути декілька відхилень від нахилу, якщо використовувати роздільник, який не є косою рисою - наприклад, s, \\|/ ,__,gзамість s/ \\|\/ /__/g.
варення

Гарна порада. Забув про цю хитрість. І кілька байтів було вирізано за допомогою заперечених наборів.
LukStorms

2

ES6 , 220 218 195 байт

Укорочений

f=d=>{var e,c=console;if(!d[79])d+=' ';c.clear();c.log(d);e=d;d=d[R='replace'](/\/\|\\/g,'a')[R](/\/ | \\/g,'__')[R](/\/\|/g,'//')[R](/\|\\/g,'\\\\')[R]('a','/|\\');if(e!=d)setTimeout(f,100,d);};

Більш читабельний

f=d=> {
    var e,
    c=console;
    if(!d[79])
        d+=' ';
    c.clear();
    c.log(d);
    e=d;
    d = d[R='replace'](/\/\|\\/g, 'a')  //Substitute '/|\' with 'a' so it doesn't get replaced
        [R](/\/ |  \\/g, '__')     //Replace '/ ' and ' \' with '__'
        [R](/\/\|/g, '//')    //Replace '/|' with '//'
        [R](/\|\\/g, '\\\\')  //Replace '|\' with '\\'
        [R]('a', '/|\\');     //Put '/|\' back
    if(e!=d)
        setTimeout(f,100,d);
};

2
Ласкаво просимо до головоломки програмування та коду для гольфу! 1. Я не впевнений, чому ви використовуєте позначення ES6. () = > {і його }()можна просто видалити з коду. 2. Я не думаю, що поля попередження є прийнятним форматом виводу для анімації. Ви можете або вбудувати JS в HTML, або внести необхідні зміни, щоб він працював з командного рядка. 3. У будь-якому випадку ваш код повинен зачекати приблизно. 100 мс між друком одного стану та іншого.
Денніс

2
Ласкаво просимо до PPCG! Я б запропонував перевірити цю публікацію та цю посаду, щоб допомогти покращити свій гольф.
jrich

Дякуємо за пропозиції та посилання на поради щодо гольфу. Це ще трохи довго, але я трохи скоротив його і додав таймер. Я детальніше ознайомлюсь із порадами і побачу, що я можу скоротити.
користувач3000806

1
Це має працювати в терміналі зараз, але оновлений стан все ще не надрукує над старим. У Linux ви можете це виправити, зателефонувавши console.log("^[c"+d)натомість, де ^[символ ESC (один байт).
Денніс

1
Якщо ви зміните перший .replaceна [R='replace'], то кожен наступний на [R], це скоротить досить небагато. Ви також можете зберегти кілька байт, використовуючи setTimeout(f,100,d)замість поточної установки.
ETHproductions

2

C #, 335 байт

Не великий вибір мови.

Я зловживав затримкою, дозволеною між 50 і 1000, щоб вибрати двоцифрове число.

Додано нові рядки та відступи для наочності:

namespace System.Threading{
    class P{
        static void Main(string[]z){
            var c=@"/|\,/|\,/|,//,|\,\\,/ ,__, \,__".Split(',');
            for(string a=z[0].PadRight(80),b="";a!=b;){
                Console.Clear();
                Console.Write(b=a);
                Thread.Sleep(99);
                a="";
                for(int i,j;(i=a.Length)<80;)
                    a+=(j=Array.FindIndex(c,d=>b.Substring(i).StartsWith(d)))%2==0
                        ?c[j+1]
                        :b.Substring(i,1);
            }
        }
    }
}

1

PHP, 175 байт

$i=sprintf("%-80s",$argv[1]);$p='preg_replace';do{echo($o=$i)."\r";$i=$p('(/\|\\\\(*SKIP)(?!)|(?|(/)\||\|(\\\\)))','$1$1',$p('(/ | \\\\)','__',$i));usleep(1e5);}while($i!=$o);

Нескорочений:

$input = sprintf("%-80s",$argv[1]);
do {
  echo $input."\r";
  $old = $input;
  $input = preg_replace('(/ | \\\\)','__',$input);
  $input = preg_replace('(/\|\\\\(*SKIP)(?!)|(?|(/)\||\|(\\\\)))','$1$1',$input);
  usleep(100000);
}
while( $input != $old);

В основному регекс-гольф. Спочатку вирівнюється будь-яке падаюче доміно, яке має простір (і через порядок відповідності зліва направо, вітер "дме"). Тоді настає потворна частина (проклинаєш коси!)

  • Зіставте /|\, а потім пропустіть його.
  • Збіг (/)|і заміни на//
  • Збіг |(\)і заміни на\\

Це змушує падіння доміно. Нарешті, просто зачекайте 100 хвилин для наступного кроку.

Використання ()в якості роздільників на регулярному виразі означає, що /s не потребує втечі, що допомагає мінімально!


Вам дозволяється чекати 50 мс замість 100, економивши 1 char;) Чи дозволяє PHP 10 ^ 5?
BlueCacti

1

Оболонка POSIX + sed, 144

sed 's/^.\{1,79\}$/& /;s/.*/printf '"'&\\r'"';sleep .1/;h;:;s,/|\\,/:\\,g;s,\(/ \| \\\),__,g;s,/|,//,g;s,|\\,\\\\,g;H;t;x;y/:/|/;s/\\/\\\\/g'|sh

Це у двох частинах. Основною роботою при доробці доміно є стандартна sedзаміна шаблону, накопичення ліній у просторі утримування. Ми тимчасово перетворюємось /|\на /:\захист, відновлюючись наприкінці.

s/^.\{0,79\}$/& /
h

:
s,/|\\,/:\\,g
s,\(/ \| \\\),__,g
s,/|,//,g
s,|\\,\\\\,g
H
t

x
y/:/|/

Оскільки sedу мене немає жодного способу вставки затримок (я заглянув у terminfo / termcap, але не зміг знайти жодного стандартного способу), я загортаю кожен рядок, printf "...\r"; sleep .1 щоб надрукувати рядок кожні 100 мс. Я фактично роблю це першим, коли у нас є лише один рядок, оскільки символи команди не будуть торкатися жодної із замін згортання.

Всі перевірені з використанням dash та GNU coreutils, з POSIXLY_CORRECTнабором у навколишньому середовищі.

Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.