Writers’ Word Bingo

Word Bingo
PHP
3

For a project for a writers’ group called Sunday writers, I spent most of yesterday making a game – Word Bingo. I wrote the code in one day, this is how.

The idea was not one that I came up with, the host of Sunday writers had the idea but I volunteered to run with it. The game was called Word Bingo. In Word Bingo, you have a bunch of words that are perhaps used a bit more often than other words. When a speaker says a word on your grid you tick it off and call out Bingo if you get a full line first.

I first encountered Word Bingo as a way to make boring meetings more interesting. Some scally-wag circulated a bingo grid filled with corporate buzz words just before a long and tedious weekly management review meeting. This time, however, we were doing it for fun.

I had the idea that it might be nice to generate a whole load of these game cards. So I wrote down a bunch of relevant words and did some maths.

The Maths of Word Bingo

On a five by five grid, there would be a total of 24 spaces. $latex 5^5-1=24$. We take one away because the centre tile is “free”.

So I knew how many tiles I needed to fill. Now I wanted to know how many different grids could I make?

If T is the total possible combination and X is the number of words in my list then: $latex T = x(x-1)(x-2)…(x-23)$

Which meant the bigger the list, the greater the number of unique grids that could be created.

First a container and some CSS

My thinking here was that I wanted something that looked good on screen but also printed out nicely. It made sense that I should specify that the page be printed A4 Landscape.

A4 Landscape is not at all obvious or perfect with web browsers. It turns out that some mighty fiddling is needed. Fortunately, as I would be running this code locally I could write CSS just for my favourite browser(s). Which was what I did. I wrote a very small PHP file with a few CSS headers and an include where the body content should go.

The CSS came in two flavours. Media-all and Media-Print for those little changes to get things working on a printed page. I’d fiddle with that on and off for the rest of the day. But not before the PHP was written.

Now for the PHP

The first thing I did was make a massive array of my words. I also chucked in a constant for the number of slots I would need to fill. Sort of like this:

// 5*5 = 25 -1 =24
define('_SLOT_SIZE_',24);

// word list

$bingo = array();

$bingo['writer'][] = 'First Person';
$bingo['writer'][] = 'Third Person';
$bingo['writer'][] = 'Past Tense';
//...
$bingo['authors'][] = 'Philip K. Dick';
$bingo['authors'][] = 'Stephen King';

If I was thinking of making this a WordPress plugin, I would probably have stored that information in a database. However I run this locally and this was quicker.

Now for some Word Bingo Logic

I realised at this stage that the quick and dirty solution of just randomly selecting 24 items from the array and relying on a large number of words to make duplicates unlikely was not going to be enough. I wanted to be safe from grids that were invalid.

I also decided that I wanted to pull different amounts of words from the different categories. Most of the words would come from the writer list but I wanted at least one author, some references to next week’s topic and some self-referential mentions of the group itself. I also wanted a smattering of joke items like “The cat walks in and sits on someone’s work”.

What was called for was a function. This is what I came up with.


// set up the random number generator
function make_seed() {
  list($usec, $sec) = explode(' ', microtime());
  return $sec + $usec * 1000000;
}
mt_srand(make_seed()); // fresh randomness

// picker for getting items from the list
function get_some($n,$what,$fallback='writer'){
   global $bingo;
   if($n>_SLOT_SIZE_){
      $n=_SLOT_SIZE_;
   } // sanity check
   if(isset($bingo[$what])){
      $list = $bingo[$what];
      $max  = count($list);
      if($n>=$max){return $list;}
      $reply = array();
      $i=0;
      while((count($reply)<$n)&&$i<_SLOT_SIZE_){
         $reply[] = $list[mt_rand(0,count($list)-1)];
         $reply=array_keys(array_flip($reply)); // remove duplicates
         ++$i;
      }
      return $reply;
   }else{
      if($what==$fallback){
         return array(); // stopping cycle of 3
      }
      return get_some($n,$fallback);
   }
}

I was quite pleased with this. I could specify a category, supply a fallback and then it would set to work. In the worst case, it would return nothing. However, it also the function also calls itself but in such a way that it will always stop within three iterations. Not just because the halting problem is solved for this code but because it was just so much more compact than I had supposed it might be. I don’t know, I was just weirdly satisfied with this function. A happy geek is a hard working geek.

Oh, you might notice that I don’t use the native array_unique() function. This is because swapping the keys and the values and allowing the duplicates to overwrite each other and then just pulling out the keys is much faster. Although it is no big deal, something about the pureness of the way that works appeals. As I said, a happy geek…

This allowed me to then fill the grid like this:

// fill the grid
$writer  = get_some(12,'writer');
$authors = get_some(1,'authors','writer');
$crude   = get_some(rand(2,5),'rude','rares');
$rares   = get_some(rand(2,4),'rares','authors');
$snv     = get_some(rand(3,5),'snv','rude');
$grid    = array();
$grid    = array_merge($writer,$authors,$crude,$rares,$snv);
$grid    = array_keys(array_flip($grid)); // remove duplicates
$grid    = refill($grid); // fill any gaps


// randomise the order
shuffle($grid);

echo "<table id='bingotable'>";
for ($x = 0; $x < 5; $x++) {
   echo '<tr>';
   for ($y = 0; $y < 5; $y++) {
      if($x==2&&$y==2){
         $free = 'Word';
         $size = sizeMe($free);
         echo '<td id="bingo"><span style="font-size:'.$size.';">Word Bingo</span></td>';
      }else{
         $item = array_shift($grid);
         $size = sizeMe($item);
         echo "<td><span class='item xy__{$x}_{$y}' style='font-size:{$size};'>{$item}</span></td>";      
      }
   }  
   echo '</tr>';
}
echo "</table>";

Sharp minds might notice that there is a function call to a function called refill. I wrote that after, it goes like this:

function refill($grid,$s=0){
   $number = count($grid);
   ++$s;
   if($s<_SLOT_SIZE_ && $number<_SLOT_SIZE_){
      $n = _SLOT_SIZE_-$number;
      ++$n;
      $more = get_some($n,'snv','rares');
      $grid = array_merge($grid,$more);
     #$grid = $grid+$more;
      $grid = array_keys(array_flip($grid));
      $grid = refill($grid,$s); // stopping cycle of slotsize+1
   }
   return $grid;
}

This is a function designed to top up the array of items that are going to be used to make sure that there are at least 24 regardless of what happens. I use the constant as an arbitrary limit and sanity check. So that could loop up to 25 times but generally, it looped once or twice if it looped at all.

The last function I added was a replacement for JQuery. I had the cells of the table resize to fill the space they had. This looked amazing on the screen but sucked on the print preview. So I decided “screw it” and did this:

function sizeMe($text){
   $size = strlen($text);
   if($size > 40){
      return "12pt";
   }
   if($size > 35){
      return "14pt";
   }
   if($size > 30){
      return "16pt";
   }
   if($size > 25){
      return "18pt";
   }
   if($size > 15){
      return "22pt";
   }
   if($size > 8){
      return "28pt";
   }
   if($size > 4){
      return "32pt";
   }
   return "38pt"; 
}

It is not the most elegant solution I have ever written. I am certain that I could have found some formula to work out the font size as an inverse ratio of the character count. What can I say, it works and looks nice on screen and in print.

That is pretty much it. I probably spent most of the time just figuring out how to make it look pretty and thinking up more words to add into my list.

All that is left to say is that you may use this code under the GNU GPL 2 and I posted a version of this to GitHub to make things a bit easier to copy.

/*
Matt's Word Bingo generator script.
Copyright (C) 2017  Matthew D Brown

This program is free software; you can redistribute it and/or
modify it under the terms of the GNU General Public License
as published by the Free Software Foundation; either version 2
of the License, or (at your option) any later version.

This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
GNU General Public License for more details.

You should have received a copy of the GNU General Public License
along with this program; if not, write to the Free Software
Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
*/
3 Comments

Leave a Reply

Your email address will not be published. Required fields are marked *

This site uses Akismet to reduce spam. Learn how your comment data is processed.

Skip to toolbar