# Bingo

Bingo is a numbers game where players match randomly drawn numbers to the numbers on their cards. Each bingo card is a square grid with 25 spaces, and the columns of the grid are labeled with letters such as "B", "I", "N", "G", "O". The letters help to identify the number range in each column, for example, the "B" column may contain numbers 1 to 15.

One of the spaces in the middle of the grid is often a "free" space, and is marked as such. To play the game, each player is given a bingo card with numbers on it. A caller then draws numbers randomly and announces them, and players mark the corresponding numbers on their cards. The first player to match a pre-determined pattern of marked numbers, such as a straight line, all four corners, or a full card, calls out "Bingo!" to win the game. The game continues until one player wins, or multiple players win on the same round. 

# Challenge

Create a set of n bingo cards that are printed / outputted in sequence separated by one or two empty lines. The center field should be marked as free.

The output should be a 5x5 grid where columns are either separated by a single space, a tab or the grid should be fixed width, and the lines are separated by a single new line.

The randomness must be uniform, i.e. all possible permutations and combinations should be equally likely to occur. All numbers must be unique.

The numbers:

B = 1 - 15
I = 16 - 30
N = 31 - 45
G = 46 - 60
O = 61 - 75


The number n must be taken as input.

## Example output for n = 3:

B I N G O
1 16 31 46 61
2 17 32 47 62
3 18 free 48 63
4 19 34 49 64
5 20 35 50 65

B I N G O
6 30 40 53 64
5 20 35 50 70
4 25 free 51 69
7 22 32 52 68
11 23 38 49 67

B I N G O
10 25 40 55 70
11 26 41 56 71
12 27 free 57 73
13 28 43 58 73
14 29 44 59 74


Yes, those were definitely randomly created.

This challenge is based on the fact that I recently had to create 100 Bingo cards. It turned out that this part of the challenge was the easy one. Making it pretty, printable and presentable was the time consuming part.

• Not a duplicate of this similarly named challenge. Jan 31 at 7:43
• Can the "BINGO" be lowercase? Jan 31 at 12:31
• Doesn't this challenge duplicate codegolf.stackexchange.com/questions/2631/bingo-card-generator ? Jan 31 at 19:57
• @gildux, No. In my opinion it's not even close. Creating a list of 5 * 15 random numbers is a whole lot different than this one. The Python answer in this one is 193 bytes while a pyhton answer in the other one is 84 bytes. That alone should show the difference, in my opinion. Feb 1 at 6:19
• In "my" language, MATLAB, golfing "create 5*15 random numbers" involves a completely different code than the code required to solve this challenge. Mixing numeric values with characters takes a bit of effort, and so does doing it several times without duplicating cards. I agree that in a language where replacing a numeric value with a character and adding a header to a numeric(?) array is trivial, then the differences aren't that big. In my, albeit biased, opnion, these are two different challenges that require two different approaches in some languages, and is therefore not a duplicate. Feb 3 at 7:23

# VyxalJ, 29 bytes

(‛₆ṫ⇧Ṅ75ɾ15ẇƛÞ℅5Ẏ;∩2≬2‛λċȦV⁋¶


Try it Online!

## Explained (old)

(        # input times:
75ɾ15ẇ   #   push the range [1, 75] split into chunks of 15 items
ƛÞ℅5Ẏ;   #   to each: random_permutation[:5]
∩        #   transpose
2≬2‛λċȦV #   tos = "free"
‛₆ṫ⇧p    #   .prepend("BINGO")
vṄ⁋      #   .map(" ".join(x)).join("\n")
¶+,      #   + "\n" (then print)


# QBasic, 211 bytes

DIM b(5,5)
RANDOMIZE TIMER
INPUT n
FOR i=1TO n
ERASE b
?
?"B","I","N","G","O
FOR r=0TO 4
FOR c=0TO 4
4x=INT((c+RND)*15+1)
FOR p=0TO r
IF x=b(p,c)GOTO 4
NEXT p
IF(r=2)*(c=2)THEN?"free",ELSE?x,:b(r,c)=x
NEXT c,r,i


Try it at Archive.org! Example run: ## Explanation/ungolfed

DIM b(5, 5)
RANDOMIZE TIMER
INPUT n


Declare b as a 5x5 array; we'll use this to track already-selected numbers and avoid duplicates. Seed the pseudo-random number generator using the current time. Prompt the user for the number of cards n.

FOR i = 1 TO n


Loop n times:

ERASE b
PRINT
PRINT "B", "I", "N", "G", "O"


Set all values in b to zero. Print a blank line followed by the B I N G O header. The commas in the PRINT statements separate the items by tabs, using a default tab stop of 14 columns.

FOR r = 0 TO 4
FOR c = 0 TO 4


Loop over rows 0 through 4; for each row, loop over columns 0 through 4.

4 x = INT((c + RND) * 15 + 1)


Label this as line number 4, which we'll use as a GOTO target later. Set x to be a random integer, greater than or equal to c*15+1, and less than (c+1)*15+1.

FOR p = 0 TO r
IF x = b(p, c) THEN GOTO 4
NEXT p


Loop over each previous row p. If x equals the value in array b at row p, column c, go back to line 4 and generate a new random number.

IF (r = 2) * (c = 2) THEN
PRINT "free",
ELSE
PRINT x,
b(r, c) = x
END IF


If both r and c are 2, print the string "free"; otherwise, print x and set the appropriate value in the b array to x. In both cases, end the print statement with a tab rather than a newline.

NEXT c, r, i


Close all the FOR loops.

Astute readers may have noticed this doesn't print a newline at the end of each row. Because of the size of the default tab stop, it just so happens that the sixth column wraps to the next line, so printing a newline explicitly is unnecessary (and, in fact, results in unwanted blank lines). This might be bending the I/O requirements a bit. If an explicit newline must be printed, modify the B I N G O line by adding ", at the end, and add a line containing ? in between the FOR r and FOR c headers.

# Factor, 123 bytes

[ [ "free"2 75 [1,b] 15 group [ 5 sample ] map flip
[ third set-nth ] keep "BINGO"1 group prefix simple-table. nl ] times ]


Try it online!

• [ ... ] times Do something a number of times.
"free"                 ! "free"
2                      ! "free" 2
75                     ! "free" 2 75
[1,b]                  ! "free" 2 { 1 2 ... 75 }
15                     ! "free" 2 { 1 2 ... 75 } 15
group                  ! "free" 2 { { 1 2 .. 15 } { 16 17 .. 30 } ... { 61 62 .. 75 } }
[ 5 sample ] map       ! "free" 2 {
!     { 9 13 14 1 5 }
!     { 26 24 25 27 22 }
!     { 32 38 41 31 33 }
!     { 54 59 55 49 57 }
!     { 75 74 70 71 63 }
! }
flip                   ! "free" 2 {
!     { 9 26 32 54 75 }
!     { 13 24 38 59 74 }
!     { 14 25 41 55 70 }
!     { 1 27 31 49 71 }
!     { 5 22 33 57 63 }
! }
[ third set-nth ] keep ! {
!     { 9 26 32 54 75 }
!     { 13 24 38 59 74 }
!     { 14 25 "free" 55 70 }
!     { 1 27 31 49 71 }
!     { 5 22 33 57 63 }
! }
"BINGO"                ! { ... } "BINGO"
1 group                ! { ... } { "B" "I" "N" "G" "O" }
prefix                 ! {
!     { "B" "I" "N" "G" "O" }
!     { 9 26 32 54 75 }
!     { 13 24 38 59 74 }
!     { 14 25 "free" 55 70 }
!     { 1 27 31 49 71 }
!     { 5 22 33 57 63 }
! }
simple-table.          ! output to stdout
nl                     ! newline


# JavaScript (ES6), 132 bytes

f=N=>(F=n=>N?n<30?F[k=n%5]^(F[k]|=1<<(v=Math.random()*15))?(n++-17?"_BINGO"[n]||k*15-~v:'free')+
[k>>2]+F(n):F(n):
+f(N-1):n)


Try it online!

### Commented

f = N => (              // outer function taking the number N of grids
F = n =>              // inner function taking a counter n
N ?                   // if there's still a grid to process:
n < 30 ?            //   if n is less than 30:
F[k = n % 5]      //     k is the column index in [0..4]
^                 //     if F[k] is not equal to its updated value
( F[k] |= 1 << (  //     where the floor(v)-th bit is set
v =           //     where v is
Math.random() //     randomly picked in [0,15[
* 15          //
)) ?              //     then:
( n++ - 17 ?    //       if this is not the center cell:
"_BINGO"[n] //         append either a BINGO letter (if n <= 5)
||          //         or
k * 15 - ~v //         the number k * 15 + floor(v + 1)
:             //       else:
'free'      //         append the word 'free'
) +             //
 \n[k >> 2] + //       append a line-feed if k = 4,
//       or a space otherwise
F(n)            //       append the result of a recursive call to F
:                 //     else:
F(n)            //       try again
:                   //   else:
\n +            //     append a line-feed followed by
f(N - 1)          //     the result of a recursive call to f
:                     // else:
n                   //   stop the recursion
)                     // initial call to F with n zero'ish

• Grids need only be separated by a single newline, so you can drop the one before the recursive call to f. Jan 31 at 17:18
• @Shaggy Not 100% sure about that. The challenge says separated by one or two *empty* lines. Jan 31 at 17:22

# Japt-mR, 39 38 bytes

75õ òF Ëö¤v5Ãg2Èh2feÃÕi"BINGO"¬ m¸·


Try it

75õ òF Ëö¤v5Ãg2Èh2feÃÕi"BINGO"¬ m¸·     :Implicit map of range [0,input)
75õ                                        :Range [1,75]
ò                                      :Partitions of length
F                                     :  15
Ë                                   :Map
ö¤                                 :  Shuffle
v5                               :  First 5 elements
Ã                              :End map
g2                            :Modify the element at 0-based index 2 by
È                           :Passing it through the following function
h2                         :  Replace the element at 0-based index 2 with
fe                     :  Compressed string "free"
Ã                    :End modification
Õ                  :Transpose
i                 :Prepend
"BINGO"¬         :  "BINGO", split into an array
m       :Map
¸      :  Join with spaces
·     :Join with newlines
:Implicit output joined with newlines

• Came up with this independently but it's pretty close to yours: 37 Feb 2 at 1:01
• 37 with explanation Feb 2 at 1:14
• Oops golfed yours to 36 Feb 2 at 1:46
• That won't work, @Jacob; A.ö(n) allows for duplicates to be included, hence why I'm shuffling and then grabbing the first 5 items. Feb 2 at 9:22

# Charcoal, 5452 49 bytes

ＦＮ«≔⪪BINGO¹θＦ²⁵⊞θ⎇⁼κ¹²free‽⁻⁺×¹⁵﹪κ⁵…¹¦¹⁶θ⟦Ｅ⪪θ⁵⪫κ


Try it online! Link is to verbose version of code. Explanation: Now inspired by @Arnauld's answer.

ＦＮ«


Input n and loop that many times.

≔⪪BINGO¹θ


Start with a list of BINGO as separate characters which will make up the first row.

Ｆ²⁵


Loop 25 times.

⊞θ⎇⁼κ¹²free‽⁻⁺×¹⁵﹪κ⁵…¹¦¹⁶θ


Put the word free in the middle cell, otherwise take a random element of the appropriate numeric range for the cell but subtracting all of the numbers generated so far, and push the result to the list.

⟦Ｅ⪪θ⁵⪫κ


Split into rows of five cells, join the rows on spaces and output the resulting board double-spaced from the next board.

# Pyth, 39 bytes

VQkjd"BINGO"jjL\ Cc5X%3s.SMc5S75y6"free


Try it online!

### Explanation

                                           # implicitly assign Q = eval(input())
VQ                                         # for N in range(Q):
k                                        # print a newline
jd"BINGO"                               # print BINGO joined on spaces
S75           # range(1,76)
c5              # split into 5 equal parts
.SM                # randomly shuffle each part
s                   # join the parts back together
%3                    # take every third element
X           y6"free    # replace the 12th element with "free"
c5                       # split into 5 equal parts again
C                         # transpose
jL\                           # join each part on whitespace, converting all elements to strings
j                              # join on newlines and print

• @Arnauld See, I knew that was part of the challenge and then forgot to do it before posting. Fixed now for a measly 3 bytes. Thanks for keeping me honest :) Jan 31 at 17:42
• OP's note: I'll allow the single leading newline. :) Feb 1 at 6:26

# 05AB1E, 32 bytes

F75L5äε.r5£N<i'€²2ǝ}}ø‘Èß‘Sš»,¶?


Try it online.

Explanation:

F                # Loop the (implicit) input-integer amount of times:
75L             #  Push a list in the range [1,75]
5ä           #  Split it into five equal-sized parts
ε          #  Map over each part:
.r        #   Randomly shuffle the list
5£      #   Then only keep the first 5 integers
N<i       #   If the index is 2:
'€²   '#    Push dictionary string "free"
2ǝ  #    Insert it at (0-based) index 2 into the quintuplet-list
}}         #  Close both the if-statement and map
ø        #  Zip/transpose the matrix; swapping rows/columns
‘Èß‘    #  Push dictionary string "BINGO"
S   #  Convert it to a list of characters: ["B","I","N","G","O"]
š  #  Prepend it to the matrix
» #  Join each inner list by spaces; and then each string by newlines
,               #  Pop and print it with trailing newline
¶?             #  And print an additional newline character


See this 05AB1E tip of mine (section How to use the dictionary?) to understand why '€² is "free" and ‘Èß‘ is "BINGO".

# Pip, 50 49 bytes

La{Y ZSH*\,75CH5y@2@2:"free"P_JsPBnMyH5PE"BINGO"}


Attempt This Online!

### Explanation

La{...}


Given command-line argument a, loop that many times:

Y ZSH*\,75CH5
\,75            ; Inclusive range from 1 to 75
CH5         ; Chop into 5 equal sections
SH*                ; Randomly shuffle each section
Z                   ; Transpose
Y                     ; Yank that result into the y variable

y@2@2:"free"          ; Set the element of y at index 2,2 to the string "free"

P_JsPBnMyH5PE"BINGO"
yH5           ; Take the first 5 elements of y
PE"BINGO"  ; Prepend the element "BINGO"
M              ; Map to each:
_Js                  ;  Join on spaces
PBn               ;  and push a newline to the end of the resulting string
P                     ; Print that list, concatenated, with a trailing newline


# Bash + GNU utils, 120

for((;j++<$1;));{ for((;++i<75;));{ a+=" <(shuf -i$i-$[i+=14] -n5)" } eval paste$a|sed '1iB	I	N	G	O
3s/\w*/free/3
$a ' }  Try it online! # APL (Dyalog Extended), 46 bytes Full program. Prompts for n. ('BINGO'{'free'}¨@3@4⍤⍪(15×…4)+⍤1⍉)⍤2↑5?¨⎕5⍴15  Try it online! ⎕5⍴15 prompt for n and join that 5, then use that as dimensions for an array of 15s 5?¨ for each 15, get 5 random numbers in the range 1 through that, without replacement ↑ mix this matrix of lists into an n-by-5-by-5 array with each row being a randome 5-of-15 ()⍤2 on each layer: ⍉ transpose, so the 5-of-15 rows become columns ()+⍤1 add the following to each row: …4 the numbers 0 through 4 15× fifteen multiplied by those 'BINGO'⍤⍪ prepend a row of the given letters, then: …@4 at row 4: …@3 at column 3: {}¨ replace each value (though there's only one) with: 'free' the given word • @Shaggy Lack of sleep, I guess. Should be alright now. – Adám Jan 31 at 23:05 # C (gcc), 262 225 bytes • -37 thanks to c-- a={L"BINGO"};f(n,i,j,k,r){--n&&puts("",f(n));for(j=5;j--;)for(k=i=0;k||++i<6;k||(a[i][j]=r))for(r=j*15+rand(k=i)%15+1;--k&&a[k][j]-r;);for(i=0;i<6;i+=puts(""))for(j=0;j<5;)printf(i*j-9?i?"%d ":"%c ":"free ",a[i][j++]);}  Try it online! Ungolfed ( used instead of literal tab character): a={L"BINGO"}; // bingo array, with header f(n, // number of cards to generate i,j, // row and column k, // random number row checker r // random number ) { --n && puts("",f(n)); // recursively print cards, adding newline for(j=5;j--;) // for each column for(k=i=0;k||++i<6;k||(a[i][j]=r)) // for each row (redo duplicates) for(r=j*15+rand(k=i)%15+1;--k&&a[k][j]-r;); // detect duplicates for(i=0;i<6;i+=puts("")) // mark middle item as free; print board for(j=0;j<5;) printf(i*j-9?i?"%d":"%c":"free",a[i][j++]); }  • it looks like you don't need to clear the array at all, because you only check for duplicates in the elements you just set – c-- Feb 1 at 20:29 • 225 bytes – c-- Feb 1 at 20:32 • 221 bytes Feb 11 at 17:58 # Jelly, 38 36 bytes 75s15Ẋ€ḣ€5Z“ƝM»€3¦€3¦“BINGO”ṭṚK€Y⁷Ṅ)  Try it online! Most of the credit goes to @cairdcoinheringaahing and @UnrelatedString for helping me in chat. • -2 thanks to UnrelatedString This can probably be a bit shorter, though. #### Explanation 75 # 75 (implicit range) s15 # Split into 15 parts Ẋ€ # Random permutation of each ḣ€5 # First 5 elements of each Z # Transpose “ƝM» # Replace ↓ with "free" €3¦€3¦ # The third item in the third item (1-indexed) “BINGO” # Push "BINGO" ṭṚ # Append and reverse (i.e. prepend) K€ # Join each by spaces Y⁷ # Join by newlines and append a newline Ṅ # Print ) # Repeat input number of times  # Python 3, 151 bytes from random import * print("B I N G O") x=lambda n:n*15+randint(0,15) [print(*[*("free"if(i==2 and j==2)else x(j)for j in range(5))])for i in range(5)]  # Raku, 94 bytes {join " ",({S:13th/\d+/free/}((<B I N G O>,|[Z] (1..75).rotor(15)».pick(5)).join(" "))xx$_)}

• (1..75).rotor(15) splits the range 1-75 into five subranges of size 15: 1-15, 16-30, etc.
• ».pick(5) selects five distinct random elements from each of those subranges.
• [Z] reduces that list of lists of random choices with the Zip operator. It effectively transposes the matrix, so the 1-15 selections are in the first column instead of the first row.
• <B I N G O>,|... prepends the header row to the lists of numbers.
• .join("\n") joins the rows with newlines, producing a Bingo board as a single string. The actual code uses a single newline character between the quotes to save a byte.
• S:13th/\d+/free/ changes the thirteenth occurrence of a number in that string to free.
• xx $_ replicates the preceding process a number of times given by the input argument $_, producing a list of board strings.
• join "\n\n" joins those boards together with two newlines between them.

# Python 3, 198 166 157 bytes

lambda n:exec('m=["BINGO",*map(list,zip(*[sample(range(i,i+15),5)for i in range(1,75,15)])),""];m="free";[print(*i)for i in m];'*n)
from random import*


Try it online!

(ATO isn't working right now so I switched to TIO)

• -32 thanks to tsh

This can probably be golfed a bit more.

• typo at line 2, need space before or Feb 1 at 18:10
• @IuriGuilherme it's not a typo - Python will let you remove the space after a number in most cases. Feb 1 at 18:17
• not in python3.11 anymore Feb 1 at 18:28
• So change to say it's Python prior 3.11 or add the space… Feb 3 at 1:36
• 166 bytes: lambda n:exec('m=["BINGO",*map(list,zip(*[sample(range(i,i+15),5)for i in range(1,75,15)])),""];m="FREE";[print(*i,sep="\t")for i in m];'*n)
– tsh
Feb 3 at 3:56