Click here to Skip to main content
15,896,269 members
Articles / Programming Languages / C#

Creating a Crossword Generator

Rate me:
Please Sign up or sign in to vote.
4.98/5 (70 votes)
11 Apr 2018CPOL2 min read 131.5K   6.6K   119  
Creating an application that can generate crosswords from a list of words
This article describes a simple application that can place a list of words, in either right-to-left or left-to-right language in a matrix, as a crossword.
using System;
using System.Collections.Generic;
using System.Linq;

namespace CrosswordCreator.Crossword
{
    public class Crossword
    {
        const string Letters = "abcdefghijklmnopqrstuvwxyz";
        readonly int[] _dirX = { 0, 1 };
        readonly int[] _dirY = { 1, 0 };
        char[,] _board;
        readonly int[,] _hWords;
        readonly int[,] _vWords;
        readonly int _n;
        readonly int _m;
        int _hCount, _vCount;
        static Random _rand;
        private static IList<string> _wordsToInsert;
        private static char[,] _tempBoard;
        private static int _bestSol;
        DateTime initialTime;

        public Crossword(int xDimen, int yDimen)
        {
            _board = new char[xDimen,yDimen];
            _hWords = new int[xDimen, yDimen];
            _vWords = new int[xDimen, yDimen];
            _n = xDimen;
            _m = yDimen;
            _rand = new Random();

            for (var i = 0; i < _n; i++)
            {
                for (var j = 0; j < _m; j++)
                {
                    _board[i, j] = ' ';
                }
            }
        }

        public override string ToString()
        {
            string result = "";
            for (int i = 0; i < _n; i++)
            {
                for (int j = 0; j < _m; j++)
                {
                    result += Letters.Contains(_board[i, j].ToString()) ? _board[i, j] : ' ';
                }
                if (i < _n - 1) result += '\n';
            }
            return result;
        }

        public char this[int i, int j]
        {
            get
            {
                return _board[i, j];
            }
            set
            {
                _board[i, j] = value;
            }
        }

        public int N
        {
            get
            {
                return _n;
            }
        }

        public int M
        {
            get
            {
                return _m;
            }
        }

        public bool inRTL{ get; set; }

        bool IsValidPosition(int x , int y)
        {
            return x >= 0 && y >= 0 && x < _n && y < _m;
        }

        int CanBePlaced(string word, int x, int y, int dir)
        {
            var result = 0;
            if (dir == 0)
            {

                for (var j = 0; j < word.Length; j++)
                {
                    int x1 = x, y1 = y + j;
                    if (!(IsValidPosition(x1, y1) && (_board[x1, y1] == ' ' || _board[x1, y1] == word[j])))
                        return -1;
                    if (IsValidPosition(x1 - 1, y1))
                        if (_hWords[x1 - 1, y1] > 0)
                            return -1;
                    if (IsValidPosition(x1 + 1, y1))
                        if (_hWords[x1 + 1, y1] > 0)
                            return -1;
                    if (_board[x1, y1] == word[j])
                        result++;
                }

            }

            else
            {
                for (var j = 0; j < word.Length; j++)
                {
                    int x1 = x + j, y1 = y;
                    if (!(IsValidPosition(x1, y1) && (_board[x1, y1] == ' ' || _board[x1, y1] == word[j])))
                        return -1;
                    if (IsValidPosition(x1, y1 - 1))
                        if (_vWords[x1, y1 - 1] > 0)
                            return -1;
                    if (IsValidPosition(x1, y1 + 1))
                        if (_vWords[x1, y1 + 1] > 0)
                            return -1;
                    if (_board[x1, y1] == word[j])
                        result++;
                }
            }

            int xStar = x - _dirX[dir], yStar = y - _dirY[dir];
            if (IsValidPosition(xStar, yStar))
                if (!(_board[xStar, yStar] == ' ' || _board[xStar, yStar] == '*'))
                    return -1;

            xStar = x + _dirX[dir]*word.Length;
            yStar = y + _dirY[dir]*word.Length;
            if (IsValidPosition(xStar, yStar))
                if (!(_board[xStar, yStar] == ' ' || _board[xStar, yStar] == '*'))
                    return -1;

            return result == word.Length ? -1 : result;

        }

        void PutWord(string word , int x , int y , int dir, int value)
        {
            var mat = dir==0 ? _hWords :_vWords;

            for (var i = 0; i < word.Length; i++)
            {
                int x1 = x + _dirX[dir]*i, y1 = y + _dirY[dir]*i;
                _board[x1, y1] = word[i];
                mat[x1, y1] = value;
            }

            int xStar = x - _dirX[dir], yStar = y - _dirY[dir];
            if (IsValidPosition(xStar, yStar)) _board[xStar, yStar] = '*';
            xStar = x + _dirX[dir]*word.Length;
            yStar = y + _dirY[dir]*word.Length;
            if (IsValidPosition(xStar, yStar)) _board[xStar, yStar] = '*';
        }

        public int AddWord(string word)
        {
            
            //var max = int.MaxValue;
            #region ubicate the word into the board
            var wordToInsert = word;
            var info = BestPosition(wordToInsert);
            if (info!=null)
            {
                if (info.Item3==0)
                {
                    _hCount++;
                    if (inRTL)
                        wordToInsert = word.Aggregate("", (x, y) => y + x);
                }
                else
                    _vCount++;
                var value = info.Item3 == 0 ? _hCount : _vCount;
                PutWord(wordToInsert , info.Item1 , info.Item2 , info.Item3 , value);
                return info.Item3;
            }
            #endregion

            return -1;

        }

        List<Tuple<int, int, int>> FindPositions(string word)
        {
            #region find best position to ubicate the word into the board
            var max = 0;
            var positions = new List<Tuple<int, int, int>>();
            for (var x = 0; x < _n; x++)
            {
                for (var y = 0; y < _m; y++)
                {
                    for (var i = 0; i < _dirX.Length; i++)
                    {
                        var dir = i;
                        var wordToInsert = i == 0 && inRTL ? word.Aggregate("", (a, b) => b + a) : word;
                        var count = CanBePlaced(wordToInsert, x, y, dir);

                        if (count < max) continue;
                        if (count > max)
                            positions.Clear();

                        max = count;
                        positions.Add(new Tuple<int, int, int>(x, y, dir));
                    }
                }
            }
            #endregion

            return positions;
        }

        Tuple<int,int,int > BestPosition(string word)
        {
            var positions = FindPositions(word);
            if (positions.Count > 0)
            {
                var index = _rand.Next(positions.Count);
                return positions[index];
            }
            return null;
        }

        public bool IsLetter(char a)
        {
            return Letters.Contains(a.ToString());
        }

        public char[,] GetBoard
        {
            get
            {
                return _board;
            }
        }

        public void Reset()
        {
            for (var i = 0; i < _n; i++)
            {
                for (var j = 0; j < _m; j++)
                {
                    _board[i, j] = ' ';
                    _vWords[i, j] = 0;
                    _hWords[i, j] = 0;
                    _hCount = _vCount = 0;
                }
            }
        }

        public void AddWords(IList<string> words)
        {
            _wordsToInsert = words;
            _bestSol = N*M;
            initialTime = DateTime.Now;
            Gen(0);
            
            _board = _tempBoard;
        }

        int FreeSpaces()
        {
            var count = 0;
            for (var i = 0; i < N; i++)
            {
                for (var j = 0; j < M; j++)
                {
                    if (_board[i, j] == ' ' || _board[i, j] == '*')
                        count++;
                }
            }
            return count;
        }

        void Gen(int pos)
        {
            
            if (pos >= _wordsToInsert.Count || (DateTime.Now-initialTime).Minutes > 1 )
                return;

            for (int i = pos; i < _wordsToInsert.Count; i++)
            {

                var posi = BestPosition(_wordsToInsert[i]);
                if (posi!=null)
                {
                    var word = _wordsToInsert[i];
                    if (posi.Item3==0 && inRTL)
                        word = word.Aggregate("", (x, y) => y + x);
                    var value = posi.Item3 == 0 ? _hCount : _vCount;
                    PutWord(word,posi.Item1,posi.Item2,posi.Item3,value);
                    Gen(pos + 1);
                    RemoveWord(word, posi.Item1, posi.Item2, posi.Item3);
                }
                else
                {
                    Gen(pos + 1);
                }
            }

            var c = FreeSpaces();
            if (c >= _bestSol) return;
            _bestSol = c;
            _tempBoard = _board.Clone() as char[,];
        }

        private void RemoveWord(string word, int x, int y, int dir)
        {
            var mat = dir == 0 ? _hWords : _vWords;
            var mat1 = dir == 0 ? _vWords : _hWords;

            for (var i = 0; i < word.Length; i++)
            {
                int x1 = x + _dirX[dir] * i, y1 = y + _dirY[dir] * i;
                if (mat1[x1, y1] == 0)
                    _board[x1, y1] = ' ';
                mat[x1, y1] = 0;
            }

            int xStar = x - _dirX[dir], yStar = y - _dirY[dir];
            if (IsValidPosition(xStar, yStar) && HasFactibleValueAround(xStar, yStar))
                _board[xStar, yStar] = ' ';
                
            xStar = x + _dirX[dir] * word.Length;
            yStar = y + _dirY[dir] * word.Length;
            if (IsValidPosition(xStar, yStar) && HasFactibleValueAround(xStar, yStar))
                _board[xStar, yStar] = ' ';
        }

        bool HasFactibleValueAround(int x , int y)
        {
            for (var i = 0; i < _dirX.Length; i++)
            {
                int x1 = x + _dirX[i] , y1 = y + _dirY[i] ;
                if (IsValidPosition(x1, y1) && (_board[x1, y1] != ' ' || _board[x1, y1] == '*'))
                    return true;
                x1 = x - _dirX[i];
                y1 = y - _dirY[i];
                if (IsValidPosition(x1, y1) && (_board[x1, y1] != ' ' || _board[x1, y1] == '*'))
                    return true;

            }
            return false;
        }


    }

}

By viewing downloads associated with this article you agree to the Terms of Service and the article's licence.

If a file you wish to view isn't highlighted, and is a text file (not binary), please let us know and we'll add colourisation support for it.

License

This article, along with any associated source code and files, is licensed under The Code Project Open License (CPOL)


Written By
CEO Secured Globe, Inc.
United States United States
Michael Haephrati is a music composer, an inventor and an expert specializes in software development and information security, who has built a unique perspective which combines technology and the end user experience. He is the author of a the book Learning C++ , which teaches C++ 20, and was published in August 2022.

He is the CEO of Secured Globe, Inc., and also active at Stack Overflow.

Read our Corporate blog or read my Personal blog.





Comments and Discussions