Connect Four Validator

20

4

Introduction

Connect Four is a game where you attempt to get four in a row: horizontally, vertically, or diagonally. In this code golf, we will be trying to find who won, given a game board. There will always be one winner, and only one winner.


Task

Given a Connect Four board, figure out who the winner is: X or Y. There will always be one winner, and only one winner. The board size will always be 6 by 7 like how the game board is in the in picture.

Given a board the following board, in this instance, X is red and Y is blue:

enter image description here

Your input would be:

OOOOOOO
OOOOOOO
OOOOOOO
OOOOXOO
OOOXXOO
OOXYYYY

You can separate rows of the game by newline character (like above), no dividing character, divide the rows into an array or list, or you can input a matrix of characters.

Correct output for this example:

Y

Y has four in a row; so, Y is the winner. So, we output Y.


Test cases

Input:

OOOOOOO
OOOOOOO
OOOOOOO
OOOOOOO
OOYYOOO
OYXXXXO

Output:

X

Input:

OOOOOOO
OOOOOOO
OOOOOOO
XXXXOOO
YXYYOOO
YXYYXYX

Output:

X

Input:

YXYYXOO
XYXXYOO
XXXYYOO
YYYXXOO
XXYYYYO
XXYYXXO

Output:

Y

Input:

OOOOOOO
OOOOOOO
OYOOOOO
OOYOOOO
OOOYOOO
OOOOYOO

Output:

Y

Input:

OOOOOOO
OOOOOOO
OYOOOOX
OOYOOOX
OOOXOOX
OXOXYOX

Output:

X

Scoring

Least number of bytes wins!

Neil

Posted 2017-04-21T00:49:29.760

Reputation: 2 417

This is the perfect challenge for PMA/Snails https://codegolf.stackexchange.com/questions/47311/language-design-2-d-pattern-matching/47495#47495

– Jerry Jeremiah – 2017-04-21T01:21:36.557

@nfnneil Maybe I will make a bounty for an answer in PMA/Snails ... – Jerry Jeremiah – 2017-04-21T01:43:12.630

3Can we assume that the winner will always have one more token than the loser? – math junkie – 2017-04-21T02:38:28.877

1@mathjunkie I was wrong, you can't assume that. – Neil – 2017-04-21T02:44:31.123

3@nfnneil does the output have to be X or Y or can we choose two other consistent outputs to indicate the winner? – Martin Ender – 2017-04-21T05:51:52.310

1Can we choose to use other characters as input? Or to input a numeric matrix? – Luis Mendo – 2017-04-21T07:42:55.203

Can we assume the four pieces of each colour will be in a connected set? – Luis Mendo – 2017-04-21T07:46:29.137

Related. – None – 2017-04-21T11:15:43.563

Almost a duplicate. It looks somewhat different, but the main body of the solution is going to be rather similar in both languages. I'm not confident enough that this is a duplicate to put in a close vote, but think this might be worth extra eyes. – None – 2017-04-21T11:23:22.617

Answers

2

Jelly, 19 bytes

UŒD;ŒD;Z;ṡ€4;/ṢEÞṪṪ

Try it online!

The core of this answer is copied from my answer to this very similar question.

Explanation

UŒD;ŒD;Z;ṡ€4;/ṢEÞṪṪ
   ;  ; ;             Append {the input} and the following three values:
UŒD                     the antidiagonals of {the input};
    ŒD                  the diagonals of {the input};
       Z                the transposed {input}.
         ṡ 4          Find all length-4 substrings
          €             of each subarray within that.
            ;/        Flatten one level.
                Þ     Sort, with the following sort order:
               E        If all elements are the same, sort later.
              Ṣ         Tiebreak via lexicographical order.
                 ṪṪ   Take the last element of the last element.

Fairly simple: we take all rows, columns, diagonals, and antidiagonals (just as in the n-queens validator), then take all length-4 substrings of those, then sort them in such a way that the winning line of 4 sorts to the end. (We need the tiebreak in case there's an OOOO in addition to the XXXX or YYYY.) Take the last element of the last element, and that'll be X or Y as required.

user62131

Posted 2017-04-21T00:49:29.760

Reputation:

6

Retina, 51 48 bytes

Thanks to Martin Ender for saving 3 bytes

M`X((.{6}X){3}|(.{8}X){3}|(.{7}X){3}|XXX)
T`d`YX

Try it Online!

Takes input as a comma-separated list of rows

math junkie

Posted 2017-04-21T00:49:29.760

Reputation: 2 490

You can save a few bytes by using a match stage and shortening (.{7}X){3}|XXX to (.{7}X|X)\4\4: https://tio.run/nexus/retina#fc4xCsMwDAXQPfcI2GC6NDS5QaeipcP/kMFDbpH47K4sJQQCqZdnf76E@/DO9ZMRwmN9FcT1WTa9Tud1LNgQ52EeYvfNSyZqJUiIJBBgU2lSY8v1qYGpQSd@0lWdQ5M27urO2/6p94W24W8fLlz72N5HupO2Xg5/

– Martin Ender – 2017-04-21T05:57:57.730

1@MartinEnder I don't see how you can use \4 - you want to repeat the effect of the .{7}, not the matched string. (And balancing groups would probably be far too long.) – Neil – 2017-04-21T07:59:15.810

1@Neil oh yeah, nevermind, somehow I didn't consider that there are other OXY cells than the match in the grid. using the match stage still saves 3 bytes then. – Martin Ender – 2017-04-21T08:00:13.983

5

Javascript (ES6), 54 55

Edit 1 byte saved thanks @Arnauld

I just check if X is the winner, as There will always be one winner, and only one winner

Input is a string with any separator, like in @Arnauld's answer

F=    
b=>'YX'[+[0,6,7,8].some(x=>b.match(`X(.{${x}}X){3}`))]

;['OOOOOOO OOOOOOO OOXOOOO OOXOOOO OOXOOOO OOXOYYY'
 ,'OOOOOOO OOOOOOO OOXOOOO OOYXOOO OOYOXOO OOYYOXY'
 ,'OOOOOOO,OOOOOOO,OOOOOOO,OOOOOOO,OOYYOOO,OYXXXXO'
 ,'OOOOOOO,OOOOOOO,OOOOOOO,XXXXOOO,YXYYOOO,YXYYXYX'
 ,'YXYYXOO,XYXXYOO,XXXYYOO,YYYXXOO,XXYYYYO,XXYYXXO']
.forEach(s => console.log(s,F(s)))

edc65

Posted 2017-04-21T00:49:29.760

Reputation: 31 086

@Arnauld right, thanks – edc65 – 2017-04-21T10:11:28.383

4

Jelly, 25 22 bytes

ŒgL⁼¥Ðf
;UŒD€;Z;$ç€4FṀ

Takes a list of strings (or list of list of characters) formed of X, Y, and O (would also work with replacements such that the space has a lower ordinal than both counters).

Try it online! or run an augmented version that takes a multiline string.

How?

ŒgL⁼¥Ðf - Link 1, runs of given length: list A, length B  e.g. "XYYYXXO", 4
Œg      - group runs of equal elements of A                     ["X","YYY","XX","O"]
     Ðf - filter keep:
    ¥   -     last two links as a dyad:
  L     -         length                                         1   3     2    1
   ⁼    -         equal to B?         (none kept in this case->) 0   0     0    0

;UŒD€;Z;$ç€4FṀ - Main link: list of list of chars (or list of stings) I
 U             - reverse each row of I
;              - I concatenated with that
  ŒD€          - positive diagonals of €ach (positive and negative diagonals)
        $      - last two links as a monad:
      Z        -     transpose of I (i.e. the columns)
       ;       -     concatenated with I (columns + rows)
     ;         - concatenate (all the required directional slices)
         ç€4   - call the last link (1) as a dyad for €ach with right argument = 4
            F  - flatten the result
             Ṁ - take the maximum ('Y'>'X'>'O') - this has the bonus effect of returning:
                               'Y' or 'X' for a winning board; and
                               'O' or '' for a (valid) game in progress.

Jonathan Allan

Posted 2017-04-21T00:49:29.760

Reputation: 67 804

4

JavaScript (ES6), 77 76 69 bytes

Saved 7 bytes thanks to Neil

Takes input as a something-separated string, where something is basically any character.

b=>[...'XXXXYYYY'].find((c,i)=>b.match(`(${c}.{${(i%4+6)%9}}){3}`+c))

Test cases

let f =

b=>[...'XXXXYYYY'].find((c,i)=>b.match(`(${c}.{${(i%4+6)%9}}){3}`+c))

console.log(f("OOOOOOO,OOOOOOO,OOOOOOO,OOOOOOO,OOYYOOO,OYXXXXO"))
console.log(f("OOOOOOO,OOOOOOO,OOOOOOO,XXXXOOO,YXYYOOO,YXYYXYX"))
console.log(f("YXYYXOO,XYXXYOO,XXXYYOO,YYYXXOO,XXYYYYO,XXYYXXO"))

Arnauld

Posted 2017-04-21T00:49:29.760

Reputation: 111 334

Why not use b.match()? Should save on the RegExp call. – Neil – 2017-04-21T08:04:33.950

@Neil I totally forgot that match() was doing an implicit conversion to RegExp. Thanks! – Arnauld – 2017-04-21T08:22:23.413

3

Python 2, 143 bytes

m=input()
u=[r[::-1]for r in m]
print"YX"[any(any('X'*4in''.join(t[i][j-i]for i in range(j+1))for j in range(6))for t in(m[::-1],m,u,u[::-1]))]

Takes a list of strings or a list of list of chars. Hard-coded for 6 rows by 7 columns, as the specification guarantees.

Try it online!

Jonathan Allan

Posted 2017-04-21T00:49:29.760

Reputation: 67 804

2

Python 2, 201 143 129 128 107 Bytes

I decided to add horizontal, vertical, and diagonal together into one list and then add increment then look for X for times in it. And since there will always be a winner, I can assume Y won if X doesn't. This codes takes a matrix of all the different pieces and empty places.

lambda m:"YX"[any("X"*4in"".join(a)for a in zip(*m)+m+zip(*["0"*(7-i)+m[i]+"00"*i+m[i]for i in range(6)]))]

Try it online!

Credits

Neil

Posted 2017-04-21T00:49:29.760

Reputation: 2 417

It's perfectly acceptable to self-answer. – Jonathan Allan – 2017-04-21T02:56:48.470

Without looking too much at it, there seems to be useless whitespaces at: i:] for, i, r, r] for and 1 for. – Yytsi – 2017-04-21T04:48:47.843

@TuukkaX Thanks for the input, updated. – Neil – 2017-04-21T04:50:59.230

Also, *(len(m)-1) could be *~-len(m). How it works.

– Yytsi – 2017-04-21T04:54:49.937

The ] for and 1 for are still there. – Yytsi – 2017-04-21T05:02:50.270

@TuukkaX fixed. – Neil – 2017-04-21T05:04:03.953

As the size of the the field is fixed, you can can replace ["0"]*~-len(m) by ["0"]*7. Then you can combine these lines in a single lambda expression. Additionally use in instead of find to check whether a string is contained in another string. Tio

– ovs – 2017-04-21T05:49:59.497

Useless space after lambda m,b: as well – ASCII-only – 2019-03-30T13:25:02.283

@ASCII-only Thanks and updated. Have a good one. – Neil – 2019-03-30T13:30:38.680

also, invalid - AFAICT you don't have a second input, and you can't pass a second input to the function if the question doesn't say so. so... back to 128 i guess?

– ASCII-only – 2019-03-30T13:33:46.093

@ASCII-only lol, did not notice that. Thanks for trying. – Neil – 2019-03-30T13:37:48.280

@ASCII-only How did you do it? Good job, thanks again. – Neil – 2019-03-30T13:48:38.333

Let us continue this discussion in chat.

– ASCII-only – 2019-03-30T13:49:13.900

2

PHP, 71 Bytes

echo preg_match('#X(XXX|(.{8}X){3}|(.{7}X){3}|(.{9}X){3})#',$argn)?X:Y;

Online Version

Jörg Hülsermann

Posted 2017-04-21T00:49:29.760

Reputation: 13 026

1

Zsh, 207 ... 158 bytes

Version history: 4 iterations for ~25 bytes first week; then 3 more iterations for ~25 bytes 6 months later, 1 more for 1 byte 11 months later.

t(){a=($^a-$^@_);for s l (${w:^^argv}){s+=$l;i=;repeat $#s a[++i]+=$s[i+1];}}
w=(+)
t $@
for s;w[++j]=${(l:j:)}_
t $@
t ${(Oa)@}
[[ $a = *XXXX* ]]&&<<<X||<<<Y

(first) (second) (third) (fourth) (fifth) (sixth) (seventh) (eigth)

In the footer, I print both the input board and the array we build from it to stderr. Scroll down to debug to see them. The array we build is a lot longer now, since t does a cartesian product with input board on every call. (Hey, it shortened the code by a few bytes.)

There's a lot to cover here, so I moved the (sixth edition) comments to an annotated gist.

(tl;dr: concatenate transpositions of the original array, but make sure to keep them separated)

GammaFunction

Posted 2017-04-21T00:49:29.760

Reputation: 2 838

1

K (ngn/k), 58 55 bytes

{"XY"@|/&/'88<x ./:/:,/{x+/:/:+3+[4#1-+!3 3]\&4}'+!6 7}

Try it online!

{ } function with argument x

+!6 7 all possible pairs of 0..5 and 0..6

{ }' for each of them do

4#1-+!3 3 are 4 of the 8 ortho-diagonal directions: (1 1;1 0;1 -1;0 1)

3+[ ]\&4 start with a list of four zeroes (&4) and make 3 steps in each of the directions

x+/:/: start from each possible position and take the steps in each possible direction

,/ concatenate. at this point we have a matrix of 4-lists of coordinate pairs, some of them extending beyond the board

x ./:/: look up the corresponding cells from x

88< which of them are "Y"-s? (88 is the ascii code of "X")

&/' which 4-lists consist only of "Y"-s? (and-reduce-each)

|/ is there at least one such? (or-reduce)

"XY"@ if false return "X", if true return "Y"

ngn

Posted 2017-04-21T00:49:29.760

Reputation: 11 449

0

05AB1E, 29 bytes

í‚εεÐ0:«NFÁ]€ø`IIø)J€€γ˜4ùS{θ

Input as a character matrix.

Try it online or verify all test cases.

Explanation:

í                 # Reverse each row of the (implicit) input-matrix
 ‚                # Pair it with the (implicit) input-matrix
  ε               # Map over both matrices:
   ε              #  Map over each row:
    Ð             #   Triplicate the current row
     0:           #   Replace all characters to a 0 (any digit would do)
       «          #   Merge the list of 0s to the current row
        NF        #   Loop the (0-based) row-index amount of times:
          Á       #    And rotate the row with appended 0s towards the right
  ]               # Close the nested maps and inner loop
   €              # For both mapped matrices:
    ø             #  Zip/transpose; swapping rows/columns
                  # (we now have a list of all diagonals and anti-diagonals)
     `            # Push both lists of lists separated to the stack
      I           # Push the input again
       Iø         # And push the input, zipped/transposed
         )        # Wrap everyone on the stack into a list
                  # (we now have a list of all diagonals, anti-diagonals, rows, and columns
          J       # Join each innermost list together to a string
           €      # For each matrix
            €     #  For each row:
             γ    #   Split it into parts with equal adjacent characters
          ˜       # Flatten all the lists to a single list
           4ù     # Only keep the parts of length 4
             S    # Flatten it to a list of characters
              {   # Sort this list of characters (in lexicographical order)
               θ  # And only leave the last one
                  # (which is output implicitly as result)

Kevin Cruijssen

Posted 2017-04-21T00:49:29.760

Reputation: 67 575

0

Python 3.8 (pre-release), 114 112 bytes

Using a bitboard approach to check for 4 in a row.
Input: a 2D list of integers, where 0 represents empty cell, 1 is X, and 2 is Y.
Output: a positive integer if X wins, or 0 if Y wins.

lambda L:(b:=sum(sum(zip(*(L+[[0]*7])),())[i]%2<<i for i in range(49)))*any((r:=b&b<<i)&r<<i*2for i in(1,6,7,8))

Try it online!

Explanation

The bitboard's layout is as follow (the missing positions in the last row is padded with 0):

0  8 16 24 32 40 48
1  9 17 25 33 41 49
2 10 18 26 34 42 50
3 11 19 27 35 43 51
4 12 20 28 36 44 52
5 13 21 29 37 45 53
6 14 22 30 38 46 54

This is the core of the function: given the bitboard b representing X's occupancies, check if X is winning:

any(
    # shift the board by i, 2i, 3i, 4i and and all of them together
    # if there's a set bit in the result (aka result != 0), then there is a 4-in-a-row
    (r:=b&b<<i)&r<<i*2 
    # do this for all 4 directions
    # 1: vertical, 7: horizontal, 6 and 8: diagonal
    for i in(1,6,7,8)
)

This part contructs the bitboard b for X:

b:=sum(
    sum(zip(*
        (L+[[0]*7]) # append a row of 0 at the bottom of L
    ),())           # flatten L into a list, with the index correspond
                    #   to the correct position in the bitboard layout
    [i]%2<<i        # set the ith bit to 1 if the new list has X (=1) at that index      
    for i in range(49)
) # sum all the set bits together to create the bitboard

Surculose Sputum

Posted 2017-04-21T00:49:29.760

Reputation: 317