text:
I done did made a spaceship maw!
Introduction:
Inspired by this comment of @MagicOctopusUrn on @Emigna's 05AB1E answer for my "It was just a bug" challenge:

8F9ÝÀNð×ý}».∊ I done did made a spaceship maw! And I was all excited about suggesting a 12-byte edit. – Magic Octopus Urn Jul 17 '17 at 20:10

Which is a 05AB1E (legacy) program resulting in this:
1234567890
1 2 3 4 5 6 7 8 9 0
1  2  3  4  5  6  7  8  9  0
1   2   3   4   5   6   7   8   9   0
1    2    3    4    5    6    7    8    9    0
1     2     3     4     5     6     7     8     9     0
1      2      3      4      5      6      7      8      9      0
1       2       3       4       5       6       7       8       9       0
1      2      3      4      5      6      7      8      9      0
1     2     3     4     5     6     7     8     9     0
1    2    3    4    5    6    7    8    9    0
1   2   3   4   5   6   7   8   9   0
1  2  3  4  5  6  7  8  9  0
1 2 3 4 5 6 7 8 9 0
1234567890

Try it online.
Challenge:
Input: A non-empty string
Output: From outwards going inwards, add one more space between each character every line, similar as done in the output above, equal to the length - 1. So for an input 1234567890 the output would actually be this instead:
1234567890
1 2 3 4 5 6 7 8 9 0
1  2  3  4  5  6  7  8  9  0
1   2   3   4   5   6   7   8   9   0
1    2    3    4    5    6    7    8    9    0
1     2     3     4     5     6     7     8     9     0
1      2      3      4      5      6      7      8      9      0
1       2       3       4       5       6       7       8       9       0
1        2        3        4        5        6        7        8        9        0
1         2         3         4         5         6         7         8         9         0
1        2        3        4        5        6        7        8        9        0
1       2       3       4       5       6       7       8       9       0
1      2      3      4      5      6      7      8      9      0
1     2     3     4     5     6     7     8     9     0
1    2    3    4    5    6    7    8    9    0
1   2   3   4   5   6   7   8   9   0
1  2  3  4  5  6  7  8  9  0
1 2 3 4 5 6 7 8 9 0
1234567890

Why? The length of 1234567890 is 10. So we start by outputting 10 lines: the first line without spaces; second with one space delimiter; third with two; etc. And then (without have the middle line with length - 1 spaces duplicated), we go back to the initial input while going down.
Challenge rules:

Input is guaranteed to be non-empty (a length >= 1). (For single char inputs we simply output that character.)
Any amount of trailing/leading spaces/newlines are allowed, as long as the output itself (wherever on the screen) is correct. (Empty line(s) in between output lines also isn't allowed.)
Input will only contain printable ASCII characters excluding whitespaces (code-point range [33, 126])
I/O is flexible. Input may be taken as STDIN, argument, or function parameter. May be a list/array/stream of characters instead of string. Output may also be a list/array/stream of characters instead of strings; may be printed to STDOUT; returned as newline-delimited string; etc.

General rules:

This is code-golf, so shortest answer in bytes wins.
Don't let code-golf languages discourage you from posting answers with non-codegolfing languages. Try to come up with an as short as possible answer for 'any' programming language.
Standard rules apply for your answer, so you are allowed to use STDIN/STDOUT, functions/method with the proper parameters and return-type, full programs. Your call.
Default Loopholes are forbidden.
If possible, please add a link with a test for your code.
Also, adding an explanation for your answer is highly recommended.

Test cases:
Input: @
Output:
@

Input: test
Output:
test
t e s t
t  e  s  t
t   e   s   t
t  e  s  t
t e s t
test

Input: ?!
Output:
?!
? !
?!

Input: Spaceship
Output:
Spaceship
S p a c e s h i p
S  p  a  c  e  s  h  i  p
S   p   a   c   e   s   h   i   p
S    p    a    c    e    s    h    i    p
S     p     a     c     e     s     h     i     p
S      p      a      c      e      s      h      i      p
S       p       a       c       e       s       h       i       p
S        p        a        c        e        s        h        i        p
S       p       a       c       e       s       h       i       p
S      p      a      c      e      s      h      i      p
S     p     a     c     e     s     h     i     p
S    p    a    c    e    s    h    i    p
S   p   a   c   e   s   h   i   p
S  p  a  c  e  s  h  i  p
S p a c e s h i p
Spaceship

Input: 05AB1E
Output:
05AB1E
0 5 A B 1 E
0  5  A  B  1  E
0   5   A   B   1   E
0    5    A    B    1    E
0     5     A     B     1     E
0    5    A    B    1    E
0   5   A   B   1   E
0  5  A  B  1  E
0 5 A B 1 E
05AB1E

Input: )}/\
Output:
)}/\
) } / \
)  }  /  \
)   }   /   \
)  }  /  \
) } / \
)}/\

Can we return a list of lines?

@EriktheOutgolfer Sure.

Gets all just jittery !!!SPACESHIP!!!

I knew I recognized that output. I love that this idea is still going.

TFW you vaguely recognize a pattern in a question ಠ_ಠ then realize it's because you accidentally made it a year ago ಠ⌣ಠ.

@MagicOctopusUrn Thanks for the inspiration. ;D

@KevinCruijssen thanks for keeping the goofy quote haha!

interesting... bugged output in a challenge inspired by bugged output

Japt, 8 6 bytes
Takes input as an array of characters, outputs an array of strings.
£qYçÃê

Try it

Explanation
£          :Map each element at (0-based) index Y
 q         :  Join input with
  Yç       :   Space repeated Y times
    Ã      :End Map
     ê     :Palindromise

Original, 8 bytes
I/O is a string. Uses the -R flag. Includes trailing spaces on each line.
¬£múYÄÃê

Try it
Explanation
             :Implicit input of string U
¬            :Split
 £           :Map each character at 0-based index Y
  m          :  Map original U
   ú         :    Right pad with spaces to length ...
    YÄ       :     Y+1
      Ã      :End map
       ê     :Palindromise
             :Implicitly join with newlines

S.ç() FTW once again :-)

Serious question: would it be feasible to search iteratively through all 1 to 6 bytes long solutions for a puzzle like this?

@filip No: there are more than 256**6=281474976710656 (at least naive) combinations. It's like guessing a password.

@KirillBulygin I guess you need some kind of heuristic to avoid useless paths. Maybe a genetic algorithm. Must research this.

@KirillBulygin, there are over 37 trillion (37,764,717,485,592) possible ways of combining the characters available in Japt into a string between 1 & 6 characters long. If you include all the other 1 byte characters which can be used in string literals or compressed strings, that number grows to over 276 trillion (276,024,445,697,280). So, no, writing a bot to generate all those then filter out the valid Japt programmes then find the one (if any exists) that works for the challenge at hand probably wouldn't be feasible. Besides where's the fun in letting a bot do your golfing for you?!

Oop, pinged the wrong person in that comment; it was meant for you @filip

@Shaggy: "where's the fun in letting a bot do your golfing for you?!" What if you made the bot really, really short?

R, 105 99 85 84 79 bytes
-6 thanks to @Kevin Cruissen and @Giuseppe
-14 from changing to a regex based method
-1 thanks to @Giuseppe
-5 thanks to @digEmALl
function(w,n=nchar(w)-1)write(trimws(Map(gsub,"",strrep(" ",n-abs(n:-n)),w)),1)

Try it online!

You can golf 1 byte by removing the space at in(r<-.

you can also swap the <- to = in the statement Kevin mentions

and you only use s once so you can just use it as an argument in write directly; bringing you down to 99 bytes

should that be a 1 rather than a "" in write? I'm digging your use of Map!

Ah yeah, thanks! write has a whole lot of parameters to misremember.

Awesome ! 79 bytes using math :)

Great solution!

I was convinced I could beat this with the collapse argument to paste but it just isn't happening...

A lookbehind Map(gsub,"(?<=.)",strrep(" ",n-abs(n:-n)),w,F,T) nearly works for 72 bytes. Unfortunately I don't know how to cat out a list in <7 bytes...

since IO is flexible you can probably just return a list of lines and do trimws(Map(gsub,"",strrep(" ",n-abs(n:-n)),w)) for 70 bytes. See for instance the two K submissions.

05AB1E, 10 9 bytes
Saved 1 bytes thanks to Adnan
εINð×ý}û»

Try it online!
Explanation
ε            # apply to each in input
 I           # push the input
  Nð×        # push <index> spaces
     ý       # merge the input on the spaces
      }      # end loop
       û     # palendromize
        »    # join on newlines

You can drop the S. Input is flexible, so inputting as list is allowed.

Tried to find something shorter, but I've only been able to find this 10-byte alternative εINúíJ}».∊ and some 11-byters like āεIsjíJ}».∊.

@KevinCruijssen: Oh yeah, this is 10 bytes as well with input as a list of chars. Was in too much of a hurry for my train that I didn't really read the fine print :)

@KevinCruijssen: Your first version is 9 bytes in legacy though, as » can be omitted.

Ah, good point. Hmm, will post it as a separate answer in that case, unless you want to add both to your answer? Either is fine by me.

Yours actually works in the legacy in 9 bytes as well if you remove » and change the loop v to a map ε.

@KevinCruijssen: I'd say yours is sufficiently different to post separately. And if you do yours in legacy we can aim for the shortest in each :)

Posted the answer. And currently Japt is beating us with 6 bytes I'm afraid. Or did you mean shortest in 05AB1E (Elixir rewrite) and 05AB1E (Python legacy)? :)

Does εINð×ý}û» also work?

@Adnan: Doh! How did I not think of û. I've used it plenty of times, while I've never used .∊ before. Thanks :)

@KevinCruijssen: Yes, that's what I meant. I very much doubt there is anything we can do to beat Japt here ;)

gÝûvyúJ, I done did made a fancier spaceship maw! And I was all excited about suggesting a 7-byte edit. (Sorry, it had to come full circle)

Python 2, 72 70 68 66 65 bytes
-2 bytes thanks to Kevin Cruijssen
-3 bytes thanks to ovs

w=input();c=s=-1
while c:print(' '*~c).join(w);s*=w[:c]>'';c+=s|1

Try it online!

c==len(w)-1 can be golfed by 1 byte with c+2>len(w). EDIT: In your new 70-byte version, 0<c can be c.

JavaScript (ES6), 53 bytes
Takes input as an array of characters.
f=(s,p=i='',o=s.join(p)+`
`)=>s[++i]?o+f(s,p+' ')+o:o

Try it online!

tried it with a string from the answer to RegEx match open tags except XHTML self-contained tags and the output looks interesting

Charcoal, 10 bytes
Ｅθ⪫θ× κ‖Ｏ↓

Try it online! Link is to verbose version of code. Explanation:
Ｅθ          Map over characters of input string
  ⪫θ        Join characters of input string using
    ×       a literal space repeated
      κ     current index number of times
            Implicitly print each result on its own line
       ‖Ｏ↓  Reflect vertically with overlap

Charcoal doesn't reflect things like [ to ]-- or is that another separate command?

@MagicOctopusUrn There are separate commands if you want to transform the reflection. See https://codegolf.stackexchange.com/a/127164 for example.

I thought I had seen it reflect before, but wasn't sure. Neat!

Canvas, 8 bytes
┐² ×＊］──

Try it here!
The 7 byte version was too good for this challenge..

I like your 7 byte version. Would you be willing to explain how it works? Which operator overlaps the two /s into a X?

@Kaya it's the last character - ─ - vertical palindromize. The palindromization of Canvas does cool things. The overlapping part has it's own character too.

Haskell, 60 59 bytes
(init<>reverse).(scanl(?)<*>tail)
a?_=do u<-a;u:[' '|' '<u]

Try it online!
Explanation
For a string (eg. "abc") we apply first
scanl (?) <*> tail

which is the same as
\str -> scanl (?) str (tail str)

This repeatedly applies (?) (appends a space to each character in the range [33..]) to the str until there are that many strings as str has characters: ["abc","a b c ", "a  b  c  "]
Now we only need to concatenate the result (minus the last element) with its reversed counter part:
init<>reverse

Ruby, 54 49 bytes
->a{(-(z=a.size-1)..z).map{|i|a*(?\s*(z-i.abs))}}

Try it online!
Takes input as an array of characters, outputs array of strings.

05AB1E (legacy), 9 bytes
εINúíJ}û»

Input as list of characters.
Try it online or verify all test cases.
Explanation:
ε     }    # Map each character in the input to:
 I         #  Take the input
  Nú       #  Prepend each with the 0-indexed amount of spaces
           #   i.e. ["t","e","s","t"] & 3 → ["   t","   e","   s","   t"]
    í      #  Reverse each item
           #   i.e. ["   t","   e","   s","   t"] → ["t   ","e   ","s   ","t   "]
     J     #  Join them together to a single string
           #   i.e. ["t   ","e   ","s   ","t   "] → "t   e   s   t   "
       û»  # Palindromize the list, and join by newlines
           #  i.e. ["test","t e s t ","t  e  s  t  ","t   e   s   t   "]
           #   → "test\nt e s t \nt  e  s  t  \nt   e   s   t   \nt  e  s  t  \nt e s t \ntest"

Oooooo, also this isn't valid because mirror changes the orientation of / to \ when mirrored, same with [, ] and (, ). May want to add those cases to catch charcoal too. (Moved the other comments to Emigna's answer, because he was the answer I originally commented on)

@MagicOctopusUrn Thanks for letting me know. Fixed it by using û» instead of .∊.

Japt, 9 8 bytes
-1 byte from @Shaggy
ÊÆ¬qXîÃê

ÊÆ¬qXîÃê        Full program, implicity input U
ÊÆ              Range from 0 to U length and map
  ¬             split U at ""
   qXîÃ         join U using " " times range current value
        ê       horizontal mirror

Try it online!

Dang; looks like you ninjaed me again! Lemme know if you'd like me to delete mine.

@Shaggy no, keep your answer, you are using array as input while I use a string so they are kind of different xD

SpX -> Xî for a 1 byte saving.

PowerShell, 66 54 bytes
-12 bytes thanks to mazzy
0..($x=($a=$args).count-1)+$x..0|gu|%{$a-join(' '*$_)}

Try it online!
Takes input via splatting, which on TIO manifests as separate command-line arguments for each character.
We first set $a=$args as the input argument. Then we set $x equal to the .count of that array -1. We then need to loop through the letters to construct the spaceship. That's done by constructing a range from 0 to $x, then $x back down to 0, then using Get-Unique to pull out just the appropriate range.
Each iteration, we take our input arguments and -join them together with the corresponding number of spaces. Each of those strings is left on the pipeline, and an implicit Write-Output gives us newlines for free when the program completes.

Try this: 0..($x=($a=$args).count-1)+$x..0|gu|%{$a-join(' '*$_)}

@mazzy What the ... how does Get-Unique work like that on the range? That's crazy! Thanks!

MATL, 25 22 13 bytes
zZv"Gtz@he!1e

Try it online!
Thanks to Luis Mendo for suggesting a 5 byte golf, which then inspired me to shave off 4 more bytes!
Explanation, with example input 'abc':
         # Implicit input, 'abc'
z        # find number of nonzero elements (length of string)
         # stack: [3]
Zv       # symmetric range
         # stack: [[1 2 3 2 1]]
"        # begin for loop, iterating over [1 2 3 2 1] as the loop indices
G        # push input
         # stack: ['abc']
tz       # dup and push length
         # stack: ['abc', 3]
@        # push loop index, i (for example, 2)
         # stack: ['abc', 3, 2]
h        # horizontally concatenate
         # stack: ['abc', [3, 2]]
e!       # reshape to matrix of 3 rows and i columns, padding with spaces, and transpose
         # stack: [['abc';'   ';'   ']]
1e       # reshape to matrix of 1 row, leaving last value on stack
         # stack: ['a  b  c  ']
         # implicit end of for loop
         # implicit end of program, display stack contents

Vyxal j, 5 bytes
ż∞v↲∩

Try it Online! -5 bytes thanks to lyxal.
ż     # range from 1 to length of input
 ∞    # Palindromised
  v↲  # For each character of the input, pad left by each of the above
    ∩ # Transpose
      # (j flag formats list of lines as a string)

Try it Online! for 6 bytes, almost 2 years later

@lyxal Found a 5

Ooh clever. Wouldn't have thought of using transpose

Oracle SQL, 115 bytes
Not a golfing language but...
SELECT TRIM(REGEXP_REPLACE(v,'(.)',LPAD('\1',1+LENGTH(v)-ABS(LEVEL-LENGTH(v)))))FROM t CONNECT BY LEVEL<2*LENGTH(v)

Assuming that the value is in column v of table t:
SQL Fiddle
Oracle 11g R2 Schema Setup:
CREATE TABLE t ( v ) AS
  SELECT 'test' FROM DUAL;

Query 1:
SELECT TRIM(REGEXP_REPLACE(v,'(.)',LPAD('\1',1+LENGTH(v)-ABS(LEVEL-LENGTH(v)))))
FROM   t
CONNECT BY LEVEL<2*LENGTH(v)

Results:
(SQLFiddle prints the values right-aligned in the column for some reason... there are no leading spaces)
| TRIM(REGEXP_REPLACE(V,'(.)',LPAD('\1',1+LENGTH(V)-ABS(LEVEL-LENGTH(V))))) |
|---------------------------------------------------------------------------|
|                                                                      test |
|                                                                   t e s t |
|                                                                t  e  s  t |
|                                                             t   e   s   t |
|                                                                t  e  s  t |
|                                                                   t e s t |
|                                                                      test |

Your SQL Fiddle results seems to only have single spaces in between characters? See this screenshot. I assume this is due to SQL Fiddle and it works locally? Btw, not sure if SQL uses standard regex rules, but can (.) be golfed to . by using \0 instead of \1 like you could in Java for example? EDIT: Never mind, that is for $0, not \0.. (Java example of what I meant).

@KevinCruijssen Click on the down arrow next the the "Run SQL" button and change the output to "Plaintext Output" or "Markdown Output" and you will see the spaces.

Thanks. It indeed looks good in that case!

Perl 5 + -M5.010 -nlF, 34 bytes
$,=$"x($#F-abs),say@F for-$#F..$#F

Try it online!

Bash, 115, 109, 105, 100, 97, 96, 92, 91, 90, 82 bytes
-5 & -3 thanks to Kevin Cruissen
-8 thanks to roblogic
for((c=f=1;f;c-=2*(f>=${#1}),f+=c))
{ printf '%-'${f}'.c' `grep -o .<<<"$1"`
echo
}

Try it online!
---
Note that since the \ is a shell escape char, the test case )}/\ should be entered with an extra \ like this: )}/\\.

Hi there. Is it possible to add a TIO-link with test case(s)? Also, I don't know Bash very well, but is it possible to remove the spaces after in and printf like in Python?

@KevinCruijssen, Thanks, see revised answer.  I'm new to TIO-link however, and am not sure how to use multiple test cases, since this bash code only inputs a string, (i.e. just one line).   The test cases all work however, though )}/\  must be single quoted like this <<< ')}/\' read s; ...etc.  .  The spaces after in and printf are needed.

Thanks. And a single test case for TIO is fine. It's mainly to verify if everything is working as expected, which indeed seems the case. +1 from me. :)

In your new version you can golf 5 more bytes like this. Spaces after for and do can be removed. f=1 can be changed to c=f=1. And f=f+c can be f+=c.

Oh, one more small thing to golf. Wasn't sure if it's possible in Bash, but apparently it is (one more reason why a TIO-link is handy ;) ), is by changing f!=0 to f in the for-loop. Just like in JavaScript and Python, 0 is falsey and every other positive/negative integer is apparently truthy in Bash.

82 bytes, using argument $1 instead of read s;

@roblogic, Good idea -- the OP did say the input was flexible.  I was fixated on STDIN...

Java (JDK 10), 115 bytes
s->{for(int l=s.length(),i=-l;++i<l;)System.out.printf(s.replaceAll(".","%-"+(i<0?l+i:l-i)+"s")+"%n",s.split(""));}

Try it online!

-2 bytes thanks to Kevin Cruijssen

l-Math.abs(i) can be golfed to i<0?l+i:l-i for -2 bytes.

@KevinCruijssen Thanks! :)

could it be l-i<0?-i:i?

@thecoder16 You would need parentheses: l-(i<0?-i:i) (12 bytes).

Jelly, 9 bytes
jⱮLḶ⁶ẋƲŒḄ

Try it online!
Returns a list of lines; output prettified over TIO.

A somewhat different 9: ,€⁶$LÐ¡ŒḄ. Other, more similar, 9's: J’⁶ẋŒḄɓjⱮ and J’⁶ẋŒḄjⱮ@ (I was looking for shorter but no joy yet)

@JonathanAllan I'm pretty sure this is optimal, I don't think there's any shorter way to write LḶ⁶ẋ or ŒḄ. However, if you manage to find a save, do ping me. :-)

Thinking about it a bit my ,€⁶$LÐ¡ŒḄ may not be valid since it has crazy nesting so might need a Y and be a full program.

@JonathanAllan Yeah, of course it's not. ['I', 'f', [[' '], 't', 'h', [['i']], 's'], ' ', 'i', ['s', ' '], 'a', [[' ', 's', 't'], 'r', ['i', 'n', 'g'], ' '], 'w', ['e', ' ', 'a', 'r', 'e'], ' ', 'd', 'o', ['o'], 'm', [[[[['e']]]]], [[[['d']]]], '!'] At least I've outgolfed 05AB1E...

I said I outgolfed 05AB1E, eh? Bah, not anymore. :/

Pyth, 12 bytes
Just my mandatory Pyth submission. I am quite proud of this so an explanation will likely come soon.
+P_=jRQ*L;_U

Try it here!
+P_=jRQ_.e*d

Try it here!

K (oK), 25 24 bytes
Solution:
,/'(1+a,1_|a:!#x)$\:+,x:

Try it online!
Explanation:
Port of my K4 solution:
,/'(1+a,1_|a:!#x)$\:+,x: / the solution
                      x: / save input as x
                     ,   / enlist
                    +    / flip
                 $\:     / pad ($) right by each-left (\:)
   (            )        / do this together
              #x         / count length of input,           e.g. 3
             !           / range 0..length,                 e.g. 0 1 2
           a:            / save as a
          |              / reverse it,                      e.g. 2 1 0
        1_               / drop first,                      e.g. 1 0
      a,                 / join to a,                       e.g. 0 1 2 1 0
    1+                   / add 1,                           e.g. 1 2 3 2 1
,/'                      / flatten (,/) each (')

Notes:

-1 byte thanks to ngn

,:' -> +,

Stax, 10 bytes
Ç·9ƒù▌╘Ä┘e

Run and debug it
Outputs with trailing whitespace on each line.
Explanation:
%R|pmx{]n(m Full program, unpacked, implicit input
%           Length of input
 R          1-based range
  |p        Palindromize
    m       Map:
     x{   m   Map over characters of input:
       ]        Character -> string
        n(      Right-pad to length given by outer map value
              Implicit flatten and output

Zsh, 95 77 bytes
Try it online!
95 bytes
j=$[$#1-1];for i ({-$j..$j})echo&&for x (${(s::)1})printf %-$[$#1-${i#-}]s $x

Takes input from argument $1. Set j to length-1. Iterate i from -j to j. For each character x in the input string, print x and append a number of spaces equal to length minus the absolute value of i

More idiomatic padding of $x (same byte-count): printf ${(r:$#1-${i#-}:)x}

Haskell, 64 60 59 bytes
(""#)
a#s|l<-(:a)=<<s,w<-' ':a=l:[x|w<(' '<$s),x<-w#s++[l]]

Try it online!
a#s                         -- take a string of spaces 'a' and the input string 's'
 |l<-(:a)=<<s               -- let 'l' be the current line, i.e. the spaces in 'a'
                            -- appended to each char in 's'
  w<-' ':a                  -- let 'w' be 'a' with an additional space
 =l                         -- return that 'l'
   :[   |w<(' '<$s)   ]     -- and, if 'w' is shorter than 's',
     x  ,x<-w#s++[l]        -- followed by a recursive call with 'w'
                            -- and by another copy of 'l'

(""#)                       -- start with an empty 'a'

PHP, 148 146 143 141 Bytes
function s($s){for(;$i<strlen($s);++$i)f($i,$s);for(--$i;--$i>=0;)f($i,$s);}function f($i,$s){echo chunk_split($s,1,str_repeat(' ',$i))."
";}

You can test it like this:
<?php
error_reporting(0);

$s = 1234567890;
function s($s){for(;$i<strlen($s);++$i)f($i,$s);for(--$i;--$i>=0;)f($i,$s);}function f($i,$s){echo chunk_split($s,1,str_repeat(' ',$i))."
";}

Output
1234567890
1 2 3 4 5 6 7 8 9 0
1  2  3  4  5  6  7  8  9  0
1   2   3   4   5   6   7   8   9   0
1    2    3    4    5    6    7    8    9    0
1     2     3     4     5     6     7     8     9     0
1      2      3      4      5      6      7      8      9      0
1       2       3       4       5       6       7       8       9       0
1        2        3        4        5        6        7        8        9        0
1         2         3         4         5         6         7         8         9         0
1        2        3        4        5        6        7        8        9        0
1       2       3       4       5       6       7       8       9       0
1      2      3      4      5      6      7      8      9      0
1     2     3     4     5     6     7     8     9     0
1    2    3    4    5    6    7    8    9    0
1   2   3   4   5   6   7   8   9   0
1  2  3  4  5  6  7  8  9  0
1 2 3 4 5 6 7 8 9 0
1234567890

Sandbox
Expanded version
 function s($s){
    //loop upwards 0-10
    for(;$i<strlen($s);++$i) f($i,$s);
     //decrement so it's odd, from 9 loop downwards to 0
    for(--$i;--$i>=0;)f($i,$s);
 }
 //2nd function to save space
 function f($i,$s){
     //chunk it, split 1 char, insert $i number of spaces
     echo chunk_split($s,1,str_repeat(' ',$i))."
";}

Attempt 2,  92 bytes
after seeing @Titus answer I reduced mine to this:
for(;++$i<2*$e=strlen($s=$argn);)echo chunk_split($s,1,str_repeat(' ',~-$e-abs($i-$e)))."
";

I was trying to think of a way to use 1 loop, instead of 2...  Believe it or not, I almost never use the for loop in "real" code.  It was the ~ bitwise Not, that I was missing...
It's sill a tiny bit longer at 92 so I don't feel so bad. But I will put it in as a second attempt anyway.
$argn is the input from the command line

Run as pipe with -nR or try it online.

Sandbox

It seems to me that your first version would be 4 bytes shorter if you used a named function f instead of assigning an anonymous one to $f - function f( saves 2 bytes over $f=function(, and you save another byte every time you call f(...) instead of $f(...). Alternatively, you could capture the $s, saving 2 bytes - ($i)use($s) is 4 bytes longer than ($i,$s), but you save 3 bytes for each call to $f($i) instead of $f($s,$i); in languages with automatic capture, like JS, this is more often a viable saving, because you don't pay the penalty of the use statement.

I do appreciate the help, I'm pretty new to code golf and don't really do it that much, one does get bored on the regular SO pages, though.  I did think about using use but it feels longer, to do that sense $i is dynamic, it would have to be passed in by reference. So it has to be use(&$i) and the $i has to be defined before passing it by reference to $f.  Which means setting it in the parent function, or before any other.  For the function it could be function s($s,$i) and just know it has to be called with s($s,0)  but it seems ugly, and that is around 11 bytes, use(&$i),$i

We could use $s though and change the \n to a real line return. That gets it to 143  2 from the line ending and 1 from use

Yes, I hadn't even considered putting $i into the use, because $s felt the "natural" capture, but it's always worth calculating the net saving. However, you can still save 2 bytes by just declaring a named function f instead of a closure: function s($s){for(;$i<strlen($s);++$i)f($i,$s);for(--$i;--$i>=0;)f($i,$s);}function f($i,$s){echo chunk_split($s,1,str_repeat(' ',$i))." ";} Extra functions like this are allowed according to this meta post: https://codegolf.meta.stackexchange.com/questions/7614/what-are-our-rules-about-additional-code-accompanying-function-submissions

Updated, saved a couple

I think your code block misses three bytes in your post's second line.

That was just a display issue in the post because of the line return.  Using a physical line return instead of "\n", saves 2 bytes (because you don't need the \ or the n) but because SO needs 4 leading spaces for a code block, I omitted those 4 leading spaces from the second line.  So the line return on here actually is \n\s\s\s\s so it will display as part of the code block, but the actual code doesn't need the spaces.  In other words, it is 141 bytes including the "}; that was outside of the code bock on the post.

Perl 6, 43 bytes
{(0....comb-1...0)>>.&{join ' 'x$^a,.comb}}

Try it online!
Returns a list of lines.
Explanation:
 {                                         }  # Anonymous code block
  (0....comb-1...0) # A list from
   0                  # 0
    ...               # to
       .comb-1        # the length of the input string -1
              ...     # back to
                 0    # 0
                   >>.&{                  }  # Map each number to
                        join        ,.comb   # Join the list of characters
                             ' 'x$^a         # With the number of spaces

Pascal (FPC), 143 135 bytes
var s:string;i,j,l:word;begin read(s);l:=length(s);repeat i:=i+1;for j:=1to l do write(s[j],'':l-abs(l-i)-1);writeln until i=l*2-1 end.

Try it online!
I will probably win only against Lenguage...

PHP, 88 89 bytes
for(;++$i<2*$e=count($a=str_split($argn));)echo join(str_pad("",~-$e-abs($i-$e)),$a),"\n";

requires PHP 5 or later for str_split. Run as pipe with -nR or try it online.

Your try-it-online link gives the wrong output I'm afraid. In your output all lines also have leading spaces, instead of only between characters (or optionally trailing). The first character should all be in the same column in the output.

@KevinCruijssen Didn´t look at the output close enough. Fixed. (Though imo my previous output looked more like a rocket) ;-)

It kinda looked like the head of an arrow to me. :D But +1 now that it's fixed.

If you place an actual newline instead of the \n, you save a byte :)

@Martijn I actually did only count one byte for it ... forgot to replace it when I pasted the code here.

@Titus - I like the bitwise not ~ I always forget about that one (and the new line).  I may steal your loop, by the way.  But not for this puzzle, I did one with 144 but I wrapped it in a function that's not required?  B4 Someone told me i had to.  I used chunk_split and str_repeat

K4, 23 bytes
Solution:
,/'(1+a,1_|a:!#x)$\:$x:

Example:
q)k),/'(1+a,1_|a:!#x)$\:$x:"Spaceship"
"Spaceship"
"S p a c e s h i p "
"S  p  a  c  e  s  h  i  p  "
"S   p   a   c   e   s   h   i   p   "
"S    p    a    c    e    s    h    i    p    "
"S     p     a     c     e     s     h     i     p     "
"S      p      a      c      e      s      h      i      p      "
"S       p       a       c       e       s       h       i       p       "
"S        p        a        c        e        s        h        i        p        "
"S       p       a       c       e       s       h       i       p       "
"S      p      a      c      e      s      h      i      p      "
"S     p     a     c     e     s     h     i     p     "
"S    p    a    c    e    s    h    i    p    "
"S   p   a   c   e   s   h   i   p   "
"S  p  a  c  e  s  h  i  p  "
"S p a c e s h i p "
"Spaceship"

Explanation:
Has trailing whitespace on each line.
,/'(1+a,1_|a:!#x)$\:$x: / the solution
                     x: / save input as x,                 e.g. "abc"
                    $   / string,                          e.g. (,"a";,"b";,"c")
                 $\:    / pad ($) right by each-left (\:)
   (            )       / do this together
              #x        / count length of input,           e.g. 3
             !          / range 0..length,                 e.g. 0 1 2
           a:           / save as a
          |             / reverse it,                      e.g. 2 1 0
        1_              / drop first,                      e.g. 1 0
      a,                / join to a,                       e.g. 0 1 2 1 0
    1+                  / add 1,                           e.g. 1 2 3 2 1
,/'                     / flatten each

C (gcc), 131 129 111 bytes
i;x;f(k,j)char*k,*j;{x=strlen(k)-1;for(i=0;i<x-~x;i+=puts(""))for(j=k;*j;)printf("%c%*s",*j++,i<x?i:2*x-i,"");}

Try it online!
-20 bytes thanks to ceilingcat!
#import<string.h>
i;x;f(k,j)char*k,*j;{x=strlen(k)-1;for(i=0;i<x-~x;i+=puts(""))for(j=k;*j;)printf("%c%*s",*j++,i<x?i:2*x-i,"");}

Try it online!
Or, if length can be accepted as a parameter:
C (gcc), 105 102 bytes
-1 byte thanks to ceilingcat!
i;x;f(k,x,j)char*k,*j;{for(i=!x--;i<x-~x;i+=puts(""))for(j=k;*j;)printf("%c%*s",*j++,i<x?i:2*x-i,"");}

Try it online!

@ceilingcat huh!

In your 102 bytes long golf, I think the global x is shadowed and thus redundantly declared.

C#, 113 105 98 bytes
s=>{for(int l=s.Length,i=-l;++i<l;)WriteLine(Join("",s.Select(c=>$"{c}".PadRight(i<0?l+i:l-i))));}

Try it online!

Hi there. Currently your answer is a snippet instead of a function or full program. This can cheaply be fixed by adding s=>{ before and } after to make it a lambda-function. In addition, one thing to golf is removing the brackets around the for-loop. Try it online.

@KevinCruijssen Thanks! Forgot to fix that before posting...

Scala, 82 bytes
for(i<-(0 to a.size)union(-a.size to 0))println(a.map(_+" "*Math.abs(i)).mkString)

Try it online
Scala has lot of shortcuts that are helping me here and that is quite readable!
Try Scala

Hi there, welcome to PPCG! Although it's a nice answer, I'm afraid two things are slightly incorrect. The line with the most spaces should only be output once in the middle instead of twice. And currently you print from 0 to length amount of spaces, instead of 0 to length-1 amount of spaces. I don't know Scala too well, but it seem you can fix both issues with +4 bytes (86 bytes in total) like this: for(i<-(0 to a.size-1)union(-a.size+2 to 0))println(a.map(_+" "*Math.abs(i)).mkString) Again welcome, and enjoy your stay! :)

8086 machine code, 56 53 bytes
00000000  bf 35 01 57 ba 01 00 52  be 82 00 b3 ff ac 59 51  |.5.W...R......YQ|
00000010  aa 3c 0d 74 07 b0 20 e2  f7 43 eb f1 b0 0a aa 59  |.<.t.. ..C.....Y|
00000020  00 d1 e3 08 38 cb d6 08  c2 51 eb dc c6 05 24 5a  |....8....Q....$Z|
00000030  b4 09 cd 21 c3                                    |...!.|
00000035

Assembled from:
org 0x100
use16
        mov di, buffer
        push di
        mov dx, 1
        push dx
nextl:  mov si, 0x82
        mov bl, -1
nextc:  lodsb
        pop cx
        push cx
stor:   stosb
        cmp al, 0x0d
        je cr
        mov al, ' '
        loop stor
        inc bx
        jmp nextc
cr:     mov al, 0x0a
        stosb
        pop cx
        add cl, dl
        jcxz done
        cmp bl, cl
        salc
        or dl, al
        push cx
        jmp nextl
done:   mov [di], byte '$'
        pop dx
        mov ah, 0x09
        int 0x21
        ret
buffer:

Test case:

Hi there. I guess there isn't any online compiled for 8086 machine code, but could you perhaps add a screenshot of the output for one of the test cases? Then I can check if everything is correct. :)

Done. Also shaved off one more byte :)

Thanks for the screenshot! Unfortunately there is one small mistake in the output. The middle line now has length amount of spaces and there are 9 lines in total, but the middle line should have length-1 amount of spaces and there should be a total of 7 lines instead (for the 4-letter word 'test'`).. :( I hope it's not to expensive in terms of bytes to fix?

Ohh I see. That's fixed now, I even reduced it by one more byte.

APL (Dyalog Extended), 28 20 bytes
{↑x,1↓⌽x←∊¨↑¨∘⍵¨⍳≢⍵}

Try it online!
-8 bytes from Adám.
Explanation
{↑x,1↓⌽x←∊¨↑¨∘⍵¨⍳≢⍵} ⍵ → input.
                ⍳≢⍵  range 1..length(input)
           ↑¨∘⍵¨     pad each input character with i spaces
         ∊¨          flatten each row
       x←            save that as x
  x,1↓⌽              join it with its reverse
 ↑                   convert to matrix(join with newlines)

20: {↑x,1↓⌽x←∊¨↑¨∘⍵¨⍳≢⍵} Try it online!

Thunno 2 N, 6 bytes
Żıṣj;Ɱ

Try it online!
Explanation
Żıṣj;Ɱ  # Implicit input
Ż       # Push [0..len(input))
 ı  ;   # Map over this list:
  ṣ     #  Push that many spaces
   j    #  Join input by this string
     Ɱ  # Then, palindromise
        # Join on newlines
        # Implicit output

Brainbash, 142 bytes
-[-[-<]>>+<]>-<++++++++++>>*,+[~+~->,+]~[-*[.~>[-<<.>>>+<]>[<->+]<<~>]~>+<<<.>>]>--[->+<<+>]>[-<+>]<<+[-*[.~>[-<<.>>>+<]>[<->+]<<~>]~>-<<<.>>]

Try it online!
In one block:
-[-[-<]>>+<]>-<++++++++++>>*,+[~+~->,+]~[-*[.~>[-<<.>>>+<]>[<->+]<<~>]~
>+<<<.>>]>--[->+<<+>]>[-<+>]<<+[-*[.~>[-<<.>>>+<]>[<->+]<<~>]~>-<<<.>>]

Explanation
-[-[-<]>>+<]>-          constant for 32
<++++++++++             constant for 10
>>                      move to counter
*                       swap tapes while retaining pointer
,+[~+~->,+]             take input in tape while keeping track of how many characters
~                       move to counter on other tape
[                       repeat counter times:
    -                       update counter
    *                       swap tapes
    [                       for each character:
        .                       output it
        ~>[                     repeat secondary counter times:
            -                       update secondary counter
            <<.                     output a space
            >>>+<                   make a copy of the secondary counter
        ]
        >[<->+]                 restore secondary counter
        <<~>                return to counter
    ]
    ~>+<<<.>>           update ternary counter and output a newline
]

>--                     fix offset

[->+<<+>]>[-<+>]<<+     various counter restoring

    [-*[.~>[-<
    <.>>>+<]>[
    <->+]<<~>]
    ~>-<<<.>>]          same as above but subtracting instead of adding

T-SQL, 215 214 bytes
DECLARE @v CHAR(32),@p VARCHAR(999),@ INT,@j INT,@k INT
SELECT @v=v,@=LEN(v),@j=-@+1FROM t
r:SET @p=''SET @k=1c:SET @p+=SUBSTRING(@v,@k,1)+SPACE(@+~ABS(@j))SET @k+=1
IF @k!>@ GOTO c
PRINT @p
SET @j+=1IF @j<@ GOTO r

Per our IO rules, input is taken from a pre-existing table t with varchar field v. Max size I've allowed is 32 characters, which tops out with the middle line being 32*31=992 characters long.
I've used a pure brute force nested-loop (using GOTO which is shorter than WHILE) with manual string construction, not very SQL-like at all. Still the best I could do (MS T-SQL has no native support for REGEX, except using CLR assemblies and manually importing System.Text.RegularExpressions).
EDIT: Here's the output for input "test":
test
t e s t
t  e  s  t
t   e   s   t
t  e  s  t
t e s t
test

And here's a partial screenshot of the output for input "abcdefghijklmnopqrstuvwxyz":

Hi there. Do you perhaps have a screenshot for one of the test cases (like 'test' for example) so I could verify everything is correct with the output?

@KevinCruijssen Added two sample outputs.

Thanks, that seems to work great. +1 from me. :)

Also, I barely know anything about T-SQL, but is it possible to golf @-ABS(@j)-1 to @+~ABS(@j)? I saw that T-SQL has a binary-not ~, but not sure if it works in the same way as Java/JavaScript/Python/etc. regarding this tip.

@KevinCruijssen Actually, that does work! Down to 214 bytes!

Pip -l, 14 9 bytes
aJsXPZ,#a

Takes the input string as a command-line argument. Try it online!
Note: the PZ operator is newer than this question.
Explanation
WIth example input abc:
           a is 1st cmdline arg; s is space (implicit)
      ,#a  Range(length(a))
           [0; 1; 2]
    PZ     Palindromize (append the reverse, but without doubling the center item)
           [0; 1; 2; 1; 0]
  sX       Repeat space that many times (vectorized)
           [""; " "; "  "; " "; ""]
aJ         Join a on those strings (vectorized)
           ["abc"; "a b c"; "a  b  c"; "a b c"; "abc"]
           Print (implicit)
           Each list element is printed on its own line, due to -l flag

Python 2, 67 bytes
s=input()
r=range(len(s))
for i in r+r[-2::-1]:print(' '*i).join(s)

Try it online!

C (gcc), 135 133 129 112 bytes
#define L{for(k=0;s[k];)printf("%c%*s",s[k++],i,"");puts("");}
i;k;f(char*s){for(i=0;s[i];i++)L for(--i;~--i;)L}

Try it online!
-2 bytes from Kevin Cruijssen
-4 bytes from ceilingcat
-17 bytes from ceilingcat
Ungolfed:
int i, k;
int f(char *s){
    for(i = 0; s[i] != '\x00'; i++) {
        for(k = 0; s[k] != '\x00'; k++) {
            printf("%c%*s", s[k], i, "");
            // print the character followed by the empty string padded with i spaces
        }
        putchar('\n');
    }
    for(i = i-2; i > -1; --i) {
        for(k = 0; s[k] != '\x00'; k++) {
            printf("%c%*s", s[k], i, "");
            // print the character followed by the empty string padded with i spaces
        }
        putchar('\n');
    }
}

You can golf 2 bytes by changing putchar(s[k]);for(j=0; to for(putchar(s[k]),j=0; so the brackets around the outer for-loop can be removed.

108 bytes

Red, 86 bytes
func[s][repeat n l: 2 *(length? s)- 1[foreach c s[prin pad c min n l - n + 1]print""]]

Try it online!

Clean, 87 81 bytes
-2 thanks to Kevin Cruijssen
import StdEnv,Text
$s#l=length s
=[concat[rpad{c}(l-abs i)' '\\c<-s]\\i<-[~l..l]]

Try it online!

Hi there. I'm afraid the output is slightly incorrect. Currently all lines also have leading spaces before the first character. The first character should all be in the same column, and the spaces should only be in between the characters (with optionally trailing spaces).

@KevinCruijssen fixed. It was a typo lpad -> rpad

Hi. I only now notice one more mistake a lot of other answers had too at first. Your middle line has length amount of spaces instead of length-1. For the 4-letter word 'test' for example, there should be 7 output lines instead of 9. You can fix that, and save 2 bytes at the same time, by removing the +1 it seems. :)

@KevinCruijssen Fixed, and thanks!

Attache, 26 bytes
Bounce@{Join[_,Iota@_*sp]}

Try it online!
Explanation
Bounce@{Join[_,Iota@_*sp]}

       {                 }    anonymous lambda. input: _ (character array)
        Join[_,         ]     join each character by (vectorizes)
               Iota@_           the indices of _ (0 ... #_)
                     *sp        repeat a space by each index
Bounce@                       bounce the results (append reverse, but middle only once)

Lua: 132 characters
a=io.read()s=' 'l=function(o,n,z)for j=o,n,z do c=''for _,i in ipairs(a)do
c=c..i..s:rep(j)end print(c)end end l(0,#a,1)l(#a-1,0,-1)

Long form:
a=io.read() -- using the input as a table, lua's version of arrays (and
-- using it like an array anyway) Not 100% sure this is how the input goes
space=' ' -- need to declare this it seems so we can repeat it a number of times
later
l = function(initialNumSpaces,maxNumSpaces,increment)
for j=initialNumSpaces, maxNumSpaces, increment do
  collector=''
  for _,i in ipairs(a)do  -- loop through the inputted string char by char
     collector=collector..i..space:rep(j) -- concat the char and spaces
  end
  print(collector) -- print the collector
end
end
l(0,#a,1) -- call the first half of the spaceship
l(#a-1,0,-1) -- call the second half of the spaceship

Hi there, welcome to PPCG! Would it be possible add a TryItOnline-link with test code for your Lua program/function? (PS: You can add a trailing # in front of your "Lua: 132 characters" to make it a title like most of the other answers. :)

JavaScript (Node.js), 102 100 98 96 bytes
f=(s,n=s.length,m=n+--n)=>[...Array(m)].map((_,i)=>[...s].join(` `.repeat(i<n?i:2*n-i))).join`

-2 by using [...]
-2 from @Kevin
-2 by tweak @Kevin
Try it online!

Hi there! I'm afraid it's slightly incorrect. With input 'test' for example you are outputting 9 lines instead of 7. The middle longest line should have length-1 amount of spaces as delimiter, but currently your middle longest line has length amount of spaces as delimiter.

changed from J to JS

You can golf n-Math.abs(i-n) to i-n<0?i:2*n-i for -2 bytes.

Welcome to PPCG! Here's a 74 byte version of your solution, taking input as an array of characters and outputting an array of lines: https://tio.run/##HcxBDsIgEADAux/prpY9eDRS48EXeGwa2QCtNHQhtDHx4tfR@ICZmV@82hLypiQ5X0ddWXe96I9iil6m7dkS0bUUfoMcBAdaOAM82oC6Y5pTEDCGMrubOAhnuYTTcS8qIGK1SdYUPcU0wQj9L2ruma1vBvxLszNYvw

@Shaggy Nice! but taking and returning array feels incomplete solution to me

It's allowed by the challenge spec: "I/O is flexible. Input may be taken as STDIN, argument, or function parameter. May be a list/array/stream of characters instead of string. Output may also be a list/array/stream of characters instead of strings; may be printed to STDOUT; returned as newline-delimited string; etc."

Rockstar, 135 133 bytes
listen to S
F takes X
cut S into L
join L with " "*X
say L

X's-1
while S at X+1
let X be+1
F taking X

while X
let X be-1
F taking X

Try it here (Code will need to be pasted in)


text:
Check my tunneling arrays
Imagine you have an array of integers, whose non-negative values are pointers to other positions in the same array, only that those values represent tunnels, so if the value in position A is positive and points to position B, then the value in position B must be also positive and point to position A to represent both ends of the tunnel. So:
Challenge

Given an array of integers, check if the array complies with the restriction to be a tunneling array and return two distinct, coherent values for truthy and falsey.
The values in the array will be below zero for non-tunnel positions, and zero or above for tunnel positions. If your array is 1-indexed, then the zero value represents a non-tunnel position. Non-tunnel values do not need to be checked.
If a positive value in a cell points to itself, that's a falsey. If A points to B, B to C and C to A, that's a falsey. If a positive value points beyond the limits of the array, that's a falsey.

Examples
The following examples are 0-indexed:
[-1, -1, -1, 6, -1, -1, 3, -1, -1]  Truthy (position 3 points to position 6 and vice versa)
[1, 0]                              Truthy (position 0 points to position 1 and vice versa)
[0, 1]                              Falsey (positions 0 and 1 point to themselves)
[4, 2, 1, -1, 0, -1]                Truthy
[2, 3, 0, 1]                        Truthy
[1, 2, 0]                           Falsey (no circular tunnels allowed)
[-1, 2, -1]                         Falsey (tunnel without end)
[]                                  Truthy (no tunnels, that's OK)
[-1, -2, -3]                        Truthy (no tunnels, that's OK)
[1, 0, 3]                           Falsey (tunnel goes beyond limits)
[1]                                 Falsey (tunnel goes beyond limits)
[1, 0, 3, 7]                        Falsey (tunnel goes beyond limits)

This is code-golf, so may the shortest code for each language win!

what should we return for [0]?

Expanding on ngn's question, are self tunnels allowed? What would the cases [0,1] and [0,-1,2] give?

@dylnan [0,1] is in the examples. "If a positive value in a cell points to itself, that's a falsey"

suggested test: [2,3,0,1]

@ngn wouldn’t that make [0] falsey?

"Imagine you have an array of integers, whose positive values..." - do you mean "non-negative values"? 2. "If your array is 1-indexed, then the zero value represents a non-tunnel position." - do you mean that if we are using 1 indexing we must handle 0 as if it were a negative value appearing in the 0-indexed version?

@dylnan not necessarily, 0 is not positive (unless you're French or Belgian)

@ngn if your array is 0-indexed then [0] is falsey, if it's 1-indexed then truthy.

@JonathanAllan the tunnel values are values indicating possible array positions. If your array is 0-indexed then every value below 0 is not a tunnel value. If it's 1-indexed then every value below 1 is not a tunnel value.

I just posted and then deleted an answer because I realized it raised an error on input of [1, 2].  Maybe this could be a test case, because we don't seem to have one yet where something points to something that points beyond the index limits.

@mathmandan see test case [1,0,3] returning falsey.

@Charlie Right, but I meant something more like [1, 0, 3, 7].  So in this example 2 points to 3, which IN TURN points outside the array.  (My deleted code correctly returned False for the [1, 0, 3] test case, but would produce an error on [1, 0, 3, 7].  So I thought I'd make the suggestion.)

@Charlie I undeleted my answer, so you can see what I'm talking about: https://codegolf.stackexchange.com/a/171786/36885

@mathmandan interesting. I have added the test case.

R, 47 bytes
function(v,a=v[v>0],b=sort(a))all(v[a]==b&a!=b)

Try it online!

Unrolled code and explanation :
f=
function(v){          # v vector of tunnel indexes (1-based) or values <= 0

  a = v[v>0]          # get the tunnel positions

  b = sort(a)         # sort the tunnel positions ascending

  c1 = v[a]==b        # get the values of 'v' at positions 'a'
                      # and check if they're equal to the sorted positions 'b'
                      # (element-wise, returns a vector of TRUE/FALSE)

  c2 = a != b         # check if positions 'a' are different from sorted positions 'b'
                      # (to exclude tunnels pointing to themselves, element-wise,
                      #  returns a vector of TRUE/FALSE)

  all(c1 & c2)        # if all logical conditions 'c1' and 'c2' are TRUE then
                      # returns TRUE otherwise FALSE
}

I would really appreciate an explanation for this answer. :-)

@Charlie : explanation added

Python 2, 66 61 60 bytes
lambda l:all(len(l)>v!=i==l[v]for i,v in enumerate(l)if-1<v)

Try it online!

APL (Dyalog Unicode), 19 24 bytes
×/<∘≢⍨×≠∘⍳∘≢⍨×0∘>∨⊢=⊢⍳⍳⍨

Try it online!
Prefix anonymous lambda, returning 1 for truthy and 0 for falsy. The TIO link contains a "prettified" version of the output for the test cases.
Shoutouts to @ngn and @Adám for saving approximately a bazillion bytes.
An extra shoutout to @ngn for the help with fixing the answer for some test cases, and with making it a train.
The updated answer uses ⎕IO←0, setting the Index Origin to 0.
How:
×/<∘≢⍨×≠∘⍳∘≢⍨×0∘>∨⊢=⊢⍳⍳⍨ ⍝ Prefix lambda, argument ⍵ → 4 2 1 ¯1 0 ¯1.
                       ⍳⍨ ⍝ Index of (⍳) ⍵ in ⍵. ⍵⍳⍵ → 0 1 2 3 4 3
                     ⊢⍳   ⍝ Index of that in ⍵ (returns the vector length if not found).
                          ⍝ ⍵⍳⍵⍳⍵ → 4 2 1 6 0 6
                  ⊢=      ⍝ Compare that with ⍵. ⍵=⍵⍳⍵⍳⍵ → 1 1 1 0 1 0
                          ⍝ This checks if positive indices tunnel back and forth correctly.
                 ∨        ⍝ Logical OR with
              0∘>         ⍝ 0>⍵ → 0 0 0 1 0 1∨1 1 1 0 1 0 → 1 1 1 1 1 1
                          ⍝ Removes the zeroes generated by negative indices
             ×            ⍝ Multiply that vector with
            ⍨             ⍝ (using ⍵ as both arguments)
         ⍳∘≢              ⍝ Generate the range [0..length(⍵)-1]
       ≠∘                 ⍝ And do ⍵≠range; this checks if any
                          ⍝ element in ⍵ is tunneling to itself.
                          ⍝ ⍵≠⍳≢⍵ → 4 2 1 ¯1 0 ¯1≠0 1 2 3 4 5 → 1 1 1 1 1 1
      ×                   ⍝ Multiply that vector with
     ⍨                    ⍝ (using ⍵ as both arguments)
  <∘≢                     ⍝ ⍵ < length(⍵) → 4 2 1 ¯1 0 ¯1 < 6 → 1 1 1 1 1 1
                          ⍝ This checks if any index is out of bounds
×/                        ⍝ Finally, multiply and reduce.
                          ⍝ ×/1 1 1 1 1 1 → 1 (truthy)

I think this doesn't work for (1), (3 2 1), (5 4 3 2 1).

0< → × I think

JavaScript (ES6), 35 bytes
Saved 1 byte thanks to @Shaggy
a=>a.every((v,i)=>v<0|v!=i&a[v]==i)

Try it online!
Commented
a =>                // a[] = input array
  a.every((v, i) => // for each value v at position i in a[]:
    v < 0 |         //   force the test to succeed if v is negative (non-tunnel position)
    v != i &        //   make sure that this cell is not pointing to itself
    a[v] == i       //   check the other end of the tunnel
  )                 // end of every()

Good thing I checked the solutions before posting a port of my Japt solution, which is nearly identical to this. You can save a byte with a=>a.every((v,i)=>v<0|v!=i&a[v]==i).

Python 2, 65 bytes
lambda l:all(l[v:]>[]and v!=i==l[v]or v<0for i,v in enumerate(l))

Try it online!

62 bytes

61 bytes

Jelly, 16 bytes
ị=JanJ$>L<$o<1$Ạ

Try it online!
1-indexed.

Japt -e, 11 bytes
§JªU¦V«aWgU

Try it

Original (w/o flag), 14 13 bytes
eÈ§JªX¦Y«aUgX

Try it or run all test cases

05AB1E, 16 15 14 bytes
εèNQyNÊ*y0‹~}P

-1 byte thanks to @Dorian.
Try it online or verify all test cases.
Explanation:
ε               # Map each value `y` of the (implicit) input-list to:
 è              #   If the current value indexed into the (implicit) input-list
  NQ            #   is equal to the index
       *        #   And
    yNÊ         #   If the current value is not equal to the current index
           ~    #  Or if:
        y0‹     #   The current value is negative
            }P  # After the map: check if everything is truthy
                # (after which the result is output implicitly)

My try was the same except with filter. I don't see a way to improve on this.

14 bytes. You can push the current value of the ε with y. So no need for ©, and each ® replaced by y

@Dorian Ah, of course.. That wasn't possible in the legacy when I posted this answer, but should have thought about it when I made my golf earlier today. Thanks! :)

Perl 6, 36 bytes
{!.grep:{2-set $++,$^v,.[$v]xx$v+1}}

Try it online!
The basic idea is to check whether the set { i, a[i], a[a[i]] } contains exactly two distinct elements for each index i with a[i] >= 0. If an element points to itself, the set contains only a single distinct element. If the other end doesn't point back to i, the set contains three distinct elements. If a[i] < 0, the xx factor is zero or negative, so the set is { i, a[i] }, also with two distinct elements.

MATL, 19 18 Bytes
-1 Byte thanks to Luis
n:G=GGG0>f))7M-|hs

Try it online!, for the first one only, because I don't know how to do all of them!
Gives 0 if truthy, a non-zero integer if falsey, eg. for test case 6 gives 4.
Please remember that like MATLAB, MATL is 1-indexed so 1 must be added to the test cases!
Never golfed in an Esolang before, so advice greatly received!
Explained:
n:G=GGG0>f))7M-|hs
                        Implicit - input array
n                       Number of values in array
 :                      Make array 1:n
  G                     Push input
   =                    Equality
n:G=                    Makes non-zero array if any of the tunnels lead to themselves
    GGG                 Push input 3x
       0                Push literal 0
        >               Greater than
      G0>               Makes array of ones where input > 0
         f              Find - returns indeces of non-zero values
                        Implicit - copy this matrix to clipboard
          )             Indeces - returns array of positive integers in order from input
           )            Ditto - Note, implicit non-zero any above maximum
            7M          Paste from clipboard
              -         Subtract
    GGG0>f))7M-         Makes array of zeros if only two-ended tunnels evident
               |        Absolute value (otherwise eg. [3,4,2,1] -> '0')
                h       Horizontal concat (ie. joins check for self tunnels and wrong tunnels)
                 s      Sum; = 0 if truthy, integer otherwise

Is my explanation too wordy? I want to make it obvious without going totally overboard.

Python, 112 97 96 86 bytes
f=lambda l:sum(i==l[i]or len(l)<=l[i]or 0<=l[i]and i!=l[l[i]]for i in range(len(l)))<1

Try it Online!
Returns True or False.
-10 bytes thanks to @Rod and @TFeld.

Java (JDK), 89 bytes
a->{int l=a.length,i=l;for(;i-->0;)i=a[i]<1||a[i]<l&&a[i]!=i&a[a[i]]==i?i:-2;return-2<i;}

Try it online!
Credits

-3 bytes thanks to AlexRacer
-6 bytes thanks to ceilingcat

Could have been 87 bytes if it weren't for that pesky IndexOutOfBoundsException. Maybe you see something to fix it easily?

@KevinCruijssen I can see how to fix that for 102 bytes. Nothing shorter yet :(

-3 bytes - omit r and break out of the loop analogous to here

K (ngn/k), 33 bytes
{*/(x<0)|(x<#x)&(~x=!#x)&x=x?x?x}

Try it online!

Groovy, 52 bytes
{o=!(i=0);it.each{e->o&=e<0||(it[e]==i&&i-e);i++};o}

Try it online!

Haskell, 48 bytes
(all=<< \u(x,y)->y<0||x/=y&&elem(y,x)u).zip[0..]

Verify all testcases!
Explanation
Let's first ungolf the code a bit. As f =<< g is the same as \x -> f (g x) x, the code is equivalent to
(\u->all(\(x,y)->y<0||x/=y&&elem(y,x)u)u).zip[0..]

which is a bit clearer.
(\u ->                                  -- given u, return
    all (\(x, y) ->                     -- whether for all elements (x, y) of u
            y < 0 ||                    -- either y < 0, or
            x /= y && elem (y, x) u     -- (x /= y) and ((y, x) is in u)
        )
    u
) . zip [0..]                           -- given the array a (implicitly via point-free style),
                                        -- return the array augmented with indices (it's the u above)

This solution is based on a simple observation: let a be the input array, and u the list of pairs (i, a[i]) where i is an index. Then a is a valid array if and only if for every (x, y) in u with y >= 0, the pair (y, x) belongs to u as well.

C (gcc), 95 94 bytes

Saved a byte thanks to ceilingcat.

i(_,o,O,Q,I)int*_;{for(I=O=0;O<o;O++)I|=_[O]>=0&&_[O]>=o|(Q=_[O[_]])<0|Q>=o|O[_]==O|Q!=O;Q=I;}

Try it online!

Charcoal, 22 bytes
¬Φθ∨⁼ικ¬∨‹ι⁰∧‹ιＬθ⁼κ§θι

Try it online! Link is to verbose version of code. Outputs - for truthy and nothing for falsy. Note: Inputting an empty array seems to crash Charcoal, but for now you can enter a space instead, which is near enough. Explanation:
  θ                     Input array
 Φ                      Filter elements
     ι                  Current value
    ⁼                   Equals
      κ                 Current index
   ∨                    Or
       ¬                Not
          ι             Current value
         ‹ ⁰            Is less than zero
        ∨               Or
              ι         Current value
             ‹          Is less than
               Ｌ        Length of
                θ       Input array
            ∧           And
                  κ     Current index
                 ⁼      Equals
                   §θι  Indexed value
¬                       Logical Not (i.e. is result empty)
                        Implicitly print

This doesn't seem to be a very Charcoalable challenge... :-)

Pascal (FPC), 165 155 153 bytes
function f(a:array of int32):byte;var i:int32;begin f:=1;for i:=0to length(a)-1do if a[i]>-1then if(a[i]=i)or(a[i]>length(a))or(a[a[i]]<>i)then f:=0;end;

Try it online!
Made function this time because the input is array. Returns 1 for truthy and 0 for falsey.

Clean, 60 bytes
import StdEnv
@l=and[v<0||l%(v,v)==[i]&&v<>i\\v<-l&i<-[0..]]

Try it online!
Clean, 142 bytes
Vastly over-complicated monster version:
import StdEnv,Data.List,Data.Maybe
$l=and[?i(mapMaybe((!?)l)j)j\\i<-l&j<-map((!?)l)l|i>=0]with?a(Just(Just c))(Just b)=a==c&&b<>c;?_ _ _=False

Try it online!
Explained:
$ l                           // function $ of `l` is
 = and [                      // true when all elements are true
  ?                           // apply ? to
   i                          // the element `i` of `l`
   (mapMaybe                  // and the result of attempting to
    ((!?)l)                   // try gettting an element from `l`
    j)                        // at the potentially invalid index `j`
   j                          // and `j` itself, which may not exist
  \\ i <- l                   // for every element `i` in `l`
  & j <- map                  // and every potential `j` in
    ((!?)l)                   // `l` trying to be indexed by
    l                         // every element in `l`
  | i >= 0                    // where `i` is greater than zero
 ]
with
 ? a (Just (Just c)) (Just b) // function ? when all the arguments exist
  = a==c && b<>c              // `a` equals `c` and not `b`
  ;
 ? _ _ _ = False              // for all other arguments, ? is false

Pyth, 17 16 bytes
.A.e|>0b&nbkq@Qb

Try it online here, or verify all the test cases at once here.
.A.e|>0b&nbkq@QbkQ   Implicit: Q=eval(input())
                     Trailing k, Q inferred
  .e             Q   Map the input with b=element, k=index, using:
     >0b               0>b
    |                  OR (
         nbk           b != k
        &              AND
            q@Qbk      Q[b] == k)
.A                   Check if all elements are truthy

Edit: realised that the trailing k was also unnecessary

Ruby, 44 bytes
->a{a.all?{|x|x<0||(w=a[x])&&x!=w&&a[w]==x}}

Try it online!

Perl 5, 54 bytes
{$i=-1;!grep$_>=0*$i++&&($_==$i||$i!=($_[$_]//-1)),@_}

Try it online!

Mathematica, 42 bytes
#=={}||(a=0@@#)[[#]]=!=a&&a[[#]][[#]]===a&

Pure function. Takes a 1-indexed list of numbers as input and returns True or False as output. Just follows the tunnels, ensuring that 0 maps to 0, no 1-cycles exist, and all cycles are 2-cycles. (I'm not entirely sure if this fails on any edge cases, but it gives the correct results for the examples.)

This answer does not work. Here for illustration purposes only.
This answer passes all of the (currently) posted test cases.  However, it fails (raises an error) on other valid input, such as [1, 2] or [1, 0, 3, 7].
How could it pass [1, 0, 3] and fail [1, 0, 3, 7]?  Well, it proceeds through the list, just like you'd expect.  When it reads an element x of the list a, it first checks whether x is less than len(a), and immediately returns False, if so.  So it correctly returns False on [1, 0, 3], because 3 is not less than len(a).
But assuming that x passes that check, the code will then go on to do some other checks, and at a certain point it happens to evaluate a[a[x]].  We've already guaranteed that evaluating a[x] will be OK...but not a[a[x]], which resolves to a[7] when x is 3 in the [1, 0, 3, 7] example.  At this point Python raises an IndexError, rather than returning False.
For completeness, here's the answer.
Python 2, 59 bytes
lambda a:all(x<len(a)>-1<a[x]!=x==a[a[x]]for x in a if-1<x)

Try it online!
I wanted to do x<len(a)and-1<a[x]..., but of course len(a) is always >-1, so the above is equivalent.  This check is a total of 5 chained relations (<, >, <, !=, and ==), plus a separate check -1<x in the if condition.
Python (conveniently) short-circuits chained relations like this, so for example if x>=len(a) then the check returns False before it gets to a[x] (which would otherwise raise an IndexError).

source: Stack Exchange

license: Creative Commons - Attribution Share-Alike - https://creativecommons.org/licenses/by-sa/4.0/

sort: votes


text:
Partially-Observable Connect-4
The Game
You will be playing an (almost) standard game of Connect-4. Unfortunately, it is a correspondence game and someone has placed black tape on every second row starting from the bottom, so that you cannot see any of your opponent's moves within these rows.
Any moves within already-full columns will count as passing your turn, and if a game runs for longer than 6 * 7 turns it will be adjudicated as a draw.
Challenge Specification
Your program should be implemented as a Python 3 function. The first argument is a 'view' of the board, representing the known board state as a 2D list of rows from bottom to top where 1 is a move by the first player, 2 a move by the second player, and 0 an empty position or a hidden move by your opponent.
The second argument is a turn number indexed from 0, and its parity tells you which player you are.
The final argument is an arbitrary state, initialized to None at the beginning of each game, which you can use to preserve state between turns.
You should return a 2-tuple of the column index you wish to play, and the new state to be returned to you next turn.
Scoring
A win counts as +1, a draw as 0, and a loss as -1. Your goal is to achieve the highest average score in a round-robin tournament. I will try to run as many matches as required to identify a clear winner.
Rules
Any competitor should have at most one competing bot at any one time, but it is OK to update your entry if you make improvements. Please try to limit your bot to ~1 second of thinking time per turn.
Testing
Here is the source code for the controller, together with a few non-competing example bots for reference:
import itertools
import random

def get_strides(board, i, j):
    yield ((i, k) for k in range(j + 1, 7))
    yield ((i, k) for k in range(j - 1, -1, -1))
    yield ((k, j) for k in range(i + 1, 6))
    yield ((k, j) for k in range(i - 1, -1, -1))
    directions = [(1, 1), (-1, -1), (1, -1), (-1, 1)]
    def diag(di, dj):
        i1 = i
        j1 = j
        while True:
            i1 += di
            if i1 < 0 or i1 >= 6:
                break
            j1 += dj
            if j1 < 0 or j1 >= 7:
                break
            yield (i1, j1)
    for d in directions:
        yield diag(*d)

DRAWN = 0
LOST = 1
WON = 2
UNDECIDED = 3

def get_outcome(board, i, j):
    if all(board[-1]):
        return DRAWN
    player = board[i][j]
    strides = get_strides(board, i, j)
    for _ in range(4):
        s0 = next(strides)
        s1 = next(strides)
        n = 1
        for s in (s0, s1):
            for i1, j1 in s:
                if board[i1][j1] == player:
                    n += 1
                    if n >= 4:
                        return WON
                else:
                    break
    return UNDECIDED

def apply_move(board, player, move):
    for i, row in enumerate(board):
        if board[i][move] == 0:
            board[i][move] = player
            outcome = get_outcome(board, i, move)
            return outcome
    if all(board[-1]):
        return DRAWN
    return UNDECIDED

def get_view(board, player):
    view = [list(row) for row in board]
    for i, row in enumerate(view):
        if i % 2:
            continue
        for j, x in enumerate(row):
            if x == 3 - player:
                row[j] = 0
    return view

def run_game(player1, player2):
    players = {1 : player1, 2 : player2}
    board = [[0] * 7 for _ in range(6)]
    states = {1 : None, 2 : None}
    for turn in range(6 * 7):
        p = (turn % 2) + 1
        player = players[p]
        view = get_view(board, p)
        move, state = player(view, turn, states[p])
        outcome = apply_move(board, p, move)
        if outcome == DRAWN:
            return DRAWN
        elif outcome == WON:
            return p
        else:
            states[p] = state
    return DRAWN

def get_score(counts):
    return (counts[WON] - counts[LOST]) / float(sum(counts))

def run_tournament(players, rounds=10000):
    counts = [[0] * 3 for _ in players]
    for r in range(rounds):
        for i, player1 in enumerate(players):
            for j, player2 in enumerate(players):
                if i == j:
                    continue
                outcome = run_game(player1, player2)
                if outcome == DRAWN:
                    for k in i, j:
                        counts[k][DRAWN] += 1
                else:
                    if outcome == 1:
                        w, l = i, j
                    else:
                        w, l = j, i
                    counts[w][WON] += 1
                    counts[l][LOST] += 1
        ranks = sorted(range(len(players)), key = lambda i: get_score(counts[i]), reverse=True)
        print("Round %d of %d\n" % (r + 1, rounds))
        rows = [("Name", "Draws", "Losses", "Wins", "Score")]
        for i in ranks:
            name = players[i].__name__
            score = get_score(counts[i])
            rows.append([name + ":"] + [str(n) for n in counts[i]] + ["%6.3f" % score])
        lengths = [max(len(s) for s in col) + 1 for col in zip(*rows)]
        for i, row in enumerate(rows):
            padding = ((n - len(s)) * ' ' for s, n in zip(row, lengths))
            print(''.join(s + p for s, p in zip(row, padding)))
            if i == 0:
                print()
        print()

def random_player(view, turn, state):
    return random.randrange(0, 7), state

def constant_player(view, turn, state):
    return 0, state

def better_random_player(view, turn, state):
    while True:
        j = random.randrange(0, 7)
        if view[-1][j] == 0:
            return j, state

def better_constant_player(view, turn, state):
    for j in range(7):
        if view[-1][j] == 0:
            return j, state

players = [random_player, constant_player, better_random_player, better_constant_player]

run_tournament(players)

Happy KoTHing!
Provisional Results
Name                    Draws Losses Wins  Score

zsani_bot:              40    5377   94583  0.892
better_constant_player: 0     28665  71335  0.427
constant_player:        3     53961  46036 -0.079
normalBot:              38    64903  35059 -0.298
better_random_player:   192   71447  28361 -0.431
random_player:          199   75411  24390 -0.510

Could you explain why you check view[-1][j] == 0? I am not entirely sure I see where you filled them and my python knowledge seems to be a bit rusty.

@Barbarian772 I'm checking if there is still space in that column. Note that there are 6 rows so the top row is fully-observed.

You shouldn’t count placing in already full columns as a pass. Many connect 4 games end with only one column not filled and if one player will lose by playing in that column, this will make the game tie when it is otherwise a forced win for one player.

@soktinpk Doesn't that just add another layer of strategy? Connect-4 is a solved game after all, so the turn skipping factor could be enough of a rule change that contributors can't just use the standard algorithms.

@soktinpk Point taken. However, I suspect that many fewer games will make it to that point given the hidden information.

Are there limitations to the "state" variable? For example, can it store information about the progress of the game in a list, or can it only be boolean?

@Solvation, it can be any data you wish to preserve between turns.

I might not understand something, but shouldn't the get_view function iterate across the row to hide values? Right now, 'if row[i] == 3 - player:' only evaluates the index of the row instead of the column.

@Solvation Wow, what a stupid bug. I should have been more careful when refactoring. Thanks.

Zero-indexing from the bottom, are the taped-over rows (0,2,4,6) or (1,3,5)? Some ASCII art would be helpful.

@Blacksilver (0,2,4,6).

This bot takes all sure wins, and falls back to block the rivals, second guess them vertically and horizontally or make random moves.

import pprint, math, collections, copy
def zsani_bot_2(view, turn, state):
    if state == None: #first own turn - always for for middle
        state = (1, 2) if turn == 0 else (2, 1) #(my_symbol, your symbol)
        #print(pprint.pformat(view) + ' Turn: ' + str(turn) + ' Player: ' + str(state[0]))
        return 3, state

    #locate obvious points
    for i in range (1, 6):              #skip first row
        for j in range(len(view[i])):   #TODO: Optimise with zip. Go for clarity now
            if view[i][j] != 0 and view[i-1][j] == 0:
                view[i-1][j] = state[1]
    enemy_points = math.floor(turn/2)
    ++enemy_points if state[0] == 2 else enemy_points
    known_points = sum([i.count(state[1]) for i in view])
    missing_points = enemy_points - known_points

    #get sure wins in any direction
    for j in range(0, 7): #every column
        for i in range(4, -1, -1):
            if view[i][j] !=0:
                break #find highest known filled point
        if (not missing_points or i+1 in {1, 3, 5}):
            view1 = copy.deepcopy(view)
            attempt = apply_move(view1, state[0], j)
            if attempt == WON:
               # print(pprint.pformat(view) + ' Turn: ' + str(turn) + ' Player: ' + str(state[0]) + ' winner move')
                return j, state

    #block sure enemy wins in any direction
    for j in range(0, 7):
        for i in range(4, -1, -1):
            if view[i][j] !=0:
                break #find highest known filled point
        if (not missing_points or (i+1 in {1, 3, 5})):
            view1 = copy.deepcopy(view)
            attempt = apply_move(view1, state[1], j)
            if attempt == WON:
              #  print(pprint.pformat(view) + ' Turn: ' + str(turn) + ' Player: ' + str(state[0]) + ' saving move')
                return j, state

    #block walls
    for i in range(0, 3): #impossible to get 4 in a row when the column is full
        for j in range(0, 6):
            if view[i][j] != 0 and view[i][j] == view[i+1][j] and view[i+2][j] == view[i+3][j] == 0:
             #   print(pprint.pformat(view) + ' Turn: ' + str(turn) + ' Player: ' + str(state[0]) + ' column move')
                return j, state

    #block platforms if posessing perfect information on row below and drop point
    for i in range(0, 5):
        for j in range(0, 3):
            stats = collections.Counter([view[i][j], view[i][j+1], view[i][j+2], view[i][j+3]])
            if stats[0] == 2 and (stats[state[0]] == 2 or stats[state[0]] == 2):
                for k in range(0, 3):
                    if view[i][j+k] == 0:
                        break
                if (i == 0 or view[i-1][j+k] != 0) and (not missing_points or i in {1, 3, 5}):
                    #print(pprint.pformat(view) + ' Turn: ' + str(turn) + ' Player: ' + str(state[0]) + ' platform move')
                    return j+k, state
                else:
                    for l in range (k, 3):
                        if view[i][j+l] == 0:
                            break
                        if (i == 0 or view[i-1][j+l] != 0) and (not missing_points or i in {1, 3, 5}):
                     #       print(pprint.pformat(view) + ' Turn: ' + str(turn) + ' Player: ' + str(state[0]) + ' platform move')
                            return j+l, state

    #fallback -> random
    while True:
        j = random.randrange(0, 7)
        if view[-1][j] == 0:
            #print(pprint.pformat(view) + ' Turn: ' + str(turn) + ' Player: ' + str(state[0]) + ' random move')
            return j, state

Thank you for fixing run_game!
Changelog:

v2 adds horizontal blocking - if, in a row of 4, there are two empty spots and two spots filled by the same player, it will attempt to fill one of them to have three in a row/block the opponents row, which will hopefully be capitalized upon in the following turns.

Welcome to the site.  I voted to reject the edit to change to code, it would be best left as a comment, that way the OP can decide what to do with the code.

I don't have enough reputation to comment on the main post. How do I withdraw an edit?

No need to withdraw the edit (I don't think you can anyway).  In the future commenting will be sufficient but since you have said it in your answer it is likely that the OP will see.  Plus I think that the OP will see that you suggested and edit even if it has been rejected.

The reason I'd like to withdraw the edit is because I missed one line in the changes, without which the edited code will completely fail to run. I've included it in the edit suggestion in my post. Thank you for your help!

normalBot plays upon the assumption that spots in the middle are more valuable than spots on the ends. Thus, it uses a normal distribution centered in the middle to determine its choices.
def normalBot(view, turn, state):
    randomNumber = round(np.random.normal(3, 1.25))
    fullColumns = []
    for i in range(7):
        if view[-1][i] != 0:
            fullColumns.append(i)
    while (randomNumber > 6) or (randomNumber < 0) or (randomNumber in fullColumns):
        randomNumber = round(np.random.normal(3, 1.25))
    return randomNumber, state

This is obviously non-competing, but nonetheless very useful for debugging... and surprisingly fun, if you know your bot well enough to cheat :D so I'm posting in the hopes of inspiring more submissions:
import math, pprint
def manual_bot(view, turn, state):
    if state == None:
        state = (1, 2) if turn == 0 else (2, 1) #(my_symbol, your symbol)

#locate obvious points
    for row in range (1, 6):
        for j in range(len(view[row])):
            if view[row][j] != 0 and view[row-1][j] == 0:
                view[row-1][j] = state[1]

#if you're second, the opponent has one more point than half the turns
    enemy_points = math.ceil(turn/2)
    known_points = sum([row.count(state[1]) for row in view])
    missing_points = enemy_points - known_points

    print(pprint.pformat(view) + ' Turn: ' + str(turn) + ' Player: ' + str(state[0]) + ' Missing points: ' + str(missing_points))
    while True:
        try:
            move = int(input("What is your move?(0-6) "))
        except ValueError:
            continue
        if move in {0, 1, 2, 3, 4, 5, 6}:
            return move, state

The grid is upside down(the bottom row is highest). To get the winner announcements, you need to patch the game controller, adding a print statement before returning the win:
elif outcome == WON:
    print(pprint.pformat(board) + ' Turn: ' + str(turn) +' Winner: '+ str(p))
    return p

[[0, 0, 0, 0, 0, 0, 0],
 [0, 0, 0, 0, 0, 0, 0],
 [0, 0, 0, 0, 0, 0, 0],
 [0, 0, 0, 0, 0, 0, 0],
 [0, 0, 0, 0, 0, 0, 0],
 [0, 0, 0, 0, 0, 0, 0]] Turn: 0 Player: 1 Missing points: 0
What is your move?(0-6) 3
[[0, 0, 0, 1, 0, 0, 0],
 [0, 0, 0, 2, 0, 0, 0],
 [0, 0, 0, 0, 0, 0, 0],
 [0, 0, 0, 0, 0, 0, 0],
 [0, 0, 0, 0, 0, 0, 0],
 [0, 0, 0, 0, 0, 0, 0]] Turn: 2 Player: 1 Missing points: 0
What is your move?(0-6) 2
[[0, 0, 1, 1, 0, 0, 0],
 [0, 0, 0, 2, 0, 0, 0],
 [0, 0, 0, 0, 0, 0, 0],
 [0, 0, 0, 0, 0, 0, 0],
 [0, 0, 0, 0, 0, 0, 0],
 [0, 0, 0, 0, 0, 0, 0]] Turn: 4 Player: 1 Missing points: 1
What is your move?(0-6) 4
[[0, 0, 1, 1, 1, 0, 0],
 [0, 0, 0, 2, 0, 0, 0],
 [0, 0, 0, 0, 0, 0, 0],
 [0, 0, 0, 0, 0, 0, 0],
 [0, 0, 0, 0, 0, 0, 0],
 [0, 0, 0, 0, 0, 0, 0]] Turn: 6 Player: 1 Missing points: 2
What is your move?(0-6) 1
[[2, 1, 1, 1, 1, 2, 0],
 [0, 0, 0, 2, 0, 0, 0],
 [0, 0, 0, 0, 0, 0, 0],
 [0, 0, 0, 0, 0, 0, 0],
 [0, 0, 0, 0, 0, 0, 0],
 [0, 0, 0, 0, 0, 0, 0]] Turn: 6 Winner: 1
[[0, 0, 0, 0, 0, 0, 0],
 [0, 0, 0, 0, 0, 0, 0],
 [0, 0, 0, 0, 0, 0, 0],
 [0, 0, 0, 0, 0, 0, 0],
 [0, 0, 0, 0, 0, 0, 0],
 [0, 0, 0, 0, 0, 0, 0]] Turn: 1 Player: 2 Missing points: 1
What is your move?(0-6) 2
[[0, 0, 2, 0, 0, 0, 0],
 [0, 0, 0, 0, 0, 0, 0],
 [0, 0, 0, 0, 0, 0, 0],
 [0, 0, 0, 0, 0, 0, 0],
 [0, 0, 0, 0, 0, 0, 0],
 [0, 0, 0, 0, 0, 0, 0]] Turn: 3 Player: 2 Missing points: 2
What is your move?(0-6) 3
[[0, 0, 2, 1, 0, 0, 0],
 [0, 0, 1, 2, 0, 0, 0],
 [0, 0, 0, 0, 0, 0, 0],
 [0, 0, 0, 0, 0, 0, 0],
 [0, 0, 0, 0, 0, 0, 0],
 [0, 0, 0, 0, 0, 0, 0]] Turn: 5 Player: 2 Missing points: 1
What is your move?(0-6) 4
[[0, 0, 2, 1, 2, 0, 0],
 [0, 0, 1, 2, 0, 0, 0],
 [0, 0, 0, 0, 0, 0, 0],
 [0, 0, 0, 0, 0, 0, 0],
 [0, 0, 0, 0, 0, 0, 0],
 [0, 0, 0, 0, 0, 0, 0]] Turn: 7 Player: 2 Missing points: 2
What is your move?(0-6) 1
[[0, 2, 2, 1, 2, 0, 0],
 [0, 0, 1, 2, 0, 0, 0],
 [0, 0, 1, 0, 0, 0, 0],
 [0, 0, 1, 0, 0, 0, 0],
 [0, 0, 0, 0, 0, 0, 0],
 [0, 0, 0, 0, 0, 0, 0]] Turn: 9 Player: 2 Missing points: 1
What is your move?(0-6) 2
[[0, 2, 2, 1, 2, 0, 0],
 [0, 0, 1, 2, 0, 0, 0],
 [0, 0, 1, 0, 0, 0, 0],
 [0, 0, 1, 0, 0, 0, 0],
 [0, 0, 2, 0, 0, 0, 0],
 [0, 0, 1, 0, 0, 0, 0]] Turn: 11 Player: 2 Missing points: 1
What is your move?(0-6) 4
[[0, 2, 2, 1, 2, 0, 0],
 [0, 0, 1, 2, 2, 0, 0],
 [0, 0, 1, 0, 0, 0, 0],
 [0, 0, 1, 0, 0, 0, 0],
 [0, 0, 2, 0, 0, 0, 0],
 [0, 0, 1, 0, 0, 0, 0]] Turn: 13 Player: 2 Missing points: 2
What is your move?(0-6) 4
[[0, 2, 2, 1, 2, 0, 0],
 [0, 1, 1, 2, 2, 0, 0],
 [0, 0, 1, 0, 1, 0, 0],
 [0, 0, 1, 0, 2, 0, 0],
 [0, 0, 2, 0, 0, 0, 0],
 [0, 0, 1, 0, 0, 0, 0]] Turn: 15 Player: 2 Missing points: 1
What is your move?(0-6) 3
[[0, 2, 2, 1, 2, 0, 0],
 [0, 1, 1, 2, 2, 0, 0],
 [0, 0, 1, 2, 1, 0, 0],
 [0, 0, 1, 0, 2, 0, 0],
 [0, 0, 2, 0, 0, 0, 0],
 [0, 0, 1, 0, 0, 0, 0]] Turn: 17 Player: 2 Missing points: 2
What is your move?(0-6) 5
[[0, 2, 2, 1, 2, 1, 1],
 [0, 1, 1, 2, 2, 2, 1],
 [0, 0, 1, 2, 1, 0, 0],
 [0, 0, 1, 0, 2, 0, 0],
 [0, 0, 2, 0, 0, 0, 0],
 [0, 0, 1, 0, 0, 0, 0]] Turn: 19 Player: 2 Missing points: 0
What is your move?(0-6)
What is your move?(0-6) 6
[[0, 2, 2, 1, 2, 1, 1],
 [0, 1, 1, 2, 2, 2, 1],
 [0, 0, 1, 2, 1, 0, 2],
 [0, 0, 1, 0, 2, 0, 0],
 [0, 0, 2, 0, 0, 0, 0],
 [0, 0, 1, 0, 0, 0, 0]] Turn: 21 Player: 2 Missing points: 1
What is your move?(0-6) 1
[[0, 2, 2, 1, 2, 1, 1],
 [0, 1, 1, 2, 2, 2, 1],
 [0, 2, 1, 2, 1, 0, 2],
 [0, 1, 1, 0, 2, 0, 0],
 [0, 0, 2, 0, 0, 0, 0],
 [0, 0, 1, 0, 0, 0, 0]] Turn: 23 Player: 2 Missing points: 1
What is your move?(0-6) 3
[[0, 2, 2, 1, 2, 1, 1],
 [0, 1, 1, 2, 2, 2, 1],
 [0, 2, 1, 2, 1, 0, 2],
 [0, 1, 1, 2, 2, 0, 0],
 [0, 0, 2, 0, 0, 0, 0],
 [0, 0, 1, 0, 0, 0, 0]] Turn: 25 Player: 2 Missing points: 2
What is your move?(0-6) 6
[[0, 2, 2, 1, 2, 1, 1],
 [0, 1, 1, 2, 2, 2, 1],
 [0, 2, 1, 2, 1, 0, 2],
 [0, 1, 1, 2, 2, 0, 2],
 [0, 0, 2, 1, 0, 0, 0],
 [0, 0, 1, 1, 0, 0, 0]] Turn: 27 Player: 2 Missing points: 1
What is your move?(0-6) 5
[[1, 2, 2, 1, 2, 1, 1],
 [1, 1, 1, 2, 2, 2, 1],
 [0, 2, 1, 2, 1, 2, 2],
 [0, 1, 1, 2, 2, 0, 2],
 [0, 0, 2, 1, 0, 0, 0],
 [0, 0, 1, 1, 0, 0, 0]] Turn: 29 Player: 2 Missing points: 0
What is your move?(0-6) 5
[[1, 2, 2, 1, 2, 1, 1],
 [1, 1, 1, 2, 2, 2, 1],
 [0, 2, 1, 2, 1, 2, 2],
 [0, 1, 1, 2, 2, 2, 2],
 [0, 0, 2, 1, 0, 0, 0],
 [0, 0, 1, 1, 0, 0, 0]] Turn: 29 Winner: 2
Round 1 of 1
Name         Draws Losses Wins Score
manual_bot:  0     0      2     1.000
zsani_bot_2: 0     2      0    -1.000

all_licenses: Creative Commons - Attribution Share-Alike - https://creativecommons.org/licenses/by-sa/4.0/