using Gtk, Colors, Cairo, Graphics

struct Guess
    code::Vector{Color}
    guess::Vector{Color}
    hint::Vector{Color}
end

function Guess(code, guess)
    len = length(code)
    hints = fill(colorant"gray", len) # gray default
    for (i, g) in enumerate(guess), (j, c) in enumerate(code)
        if g == c
            if i == j
                hints[i] = colorant"black"
            elseif hints[i] != colorant"black"
                hints[i] = colorant"white"
            end
        end
    end
    g = Guess([c for c in code], [c for c in guess], [c for c in hints])
end

tovec(guess) = [x for x in [guess.code; guess.guess; guess.hint]]

function mastermindapp()
    allu(s) = length(unique(s)) == length(s)
    ccode(c, n, rok) = while true a = rand(c, n); if rok || allu(a) return a end end

    numcolors, codelength, maxguesses, allowrep, gameover = 10, 4, 10, false, false
    colors = distinguishable_colors(numcolors)
    code = ccode(colors, numcolors, allowrep)
    guesshistory = Vector{Guess}()

    win = GtkWindow("Mastermind Game", 900, 750) |> (GtkFrame() |> (box = GtkBox(:v)))
    settingsbox = GtkBox(:h)
    playtoolbar = GtkToolbar()

    setcolors = GtkScale(false, 4:20)
    set_gtk_property!(setcolors, :hexpand, true)
    adj = GtkAdjustment(setcolors)
    set_gtk_property!(adj, :value, 10)
    clabel = GtkLabel("Number of Colors")

    setcodelength = GtkScale(false, 4:10)
    set_gtk_property!(setcodelength, :hexpand, true)
    adj = GtkAdjustment(setcodelength)
    set_gtk_property!(adj, :value, 4)
    slabel = GtkLabel("Code Length")

    setnumguesses = GtkScale(false, 4:40)
    set_gtk_property!(setnumguesses, :hexpand, true)
    adj = GtkAdjustment(setnumguesses)
    set_gtk_property!(adj, :value, 10)
    nlabel = GtkLabel("Max Guesses")

    allowrepeatcolor = GtkScale(false, 0:1)
    set_gtk_property!(allowrepeatcolor, :hexpand, true)
    rlabel = GtkLabel("Allow Repeated Colors (0 = No)")

    newgame = GtkToolButton("New Game")
    set_gtk_property!(newgame, :label, "New Game")
    set_gtk_property!(newgame, :is_important, true)

    tryguess = GtkToolButton("Submit Current Guess")
    set_gtk_property!(tryguess, :label, "Submit Current Guess")
    set_gtk_property!(tryguess, :is_important, true)

    eraselast = GtkToolButton("Erase Last (Unsubmitted) Pick")
    set_gtk_property!(eraselast, :label, "Erase Last (Unsubmitted) Pick")
    set_gtk_property!(eraselast, :is_important, true)

    map(w->push!(settingsbox, w),[clabel, setcolors, slabel, setcodelength,
        nlabel, setnumguesses, rlabel, allowrepeatcolor])
    map(w->push!(playtoolbar, w),[newgame, tryguess, eraselast])

    scrwin = GtkScrolledWindow()
    can = GtkCanvas()
    set_gtk_property!(can, :expand, true)
    map(w -> push!(box, w),[settingsbox, playtoolbar, scrwin])
    push!(scrwin, can)

    currentguess = RGB[]
    guessesused = 0
    colorpositions = Point[]

    function newgame!(w)
        empty!(guesshistory)

        numcolors = Int(GAccessor.value(setcolors))
        codelength = Int(GAccessor.value(setcodelength))
        maxguesses = Int(GAccessor.value(setnumguesses))
        allowrep = Int(GAccessor.value(allowrepeatcolor))

        colors = distinguishable_colors(numcolors)
        code = ccode(colors, codelength, allowrep == 1)
        empty!(currentguess)
        currentneeded = codelength
        guessesused = 0
        gameover = false
        draw(can)
    end
    signal_connect(newgame!, newgame, :clicked)

    function saywon!()
        warn_dialog("You have WON the game!", win)
        gameover = true
    end

    function outofguesses!()
        warn_dialog("You have Run out of moves! Game over.", win)
        gameover = true
    end

    can.mouse.button1press = @guarded (widget, event) -> begin
        if !gameover && (i = findfirst(p ->
            sqrt((p.x - event.x)^2 + (p.y - event.y)^2) < 20,
                colorpositions)) != nothing
            if length(currentguess) < codelength
                if allowrep == 0 && !allu(currentguess)
                    warn_dialog("Please erase the duplicate color.", win)
                else
                    push!(currentguess, colors[i])
                    draw(can)
                end
            else
                warn_dialog("You need to submit this guess if ready.", win)
            end
        end
    end

    @guarded draw(can) do widget
        ctx = Gtk.getgc(can)
        select_font_face(ctx, "Courier", Cairo.FONT_SLANT_NORMAL, Cairo.FONT_WEIGHT_BOLD)
        fontpointsize = 12
        set_font_size(ctx, fontpointsize)
        workcolor = colorant"black"
        set_source(ctx, workcolor)
        move_to(ctx, 0, fontpointsize)
        show_text(ctx, "Color options: " * "-"^70)
        stroke(ctx)
        empty!(colorpositions)
        for i in 1:numcolors
            set_source(ctx, colors[i])
            circle(ctx, i * 40, 40, 20)
            push!(colorpositions, Point(i * 40, 40))
            fill(ctx)
        end
        set_gtk_property!(can, :expand, false) # kludge for good text overwriting
        move_to(ctx, 0, 80)
        set_source(ctx, workcolor)
        show_text(ctx, string(maxguesses - guessesused, pad = 2) * " moves remaining.")
        stroke(ctx)
        set_gtk_property!(can, :expand, true)
        for i in 1:codelength
            set_source(ctx, i > length(currentguess) ? colorant"lightgray" : currentguess[i])
            circle(ctx, i * 40, 110, 20)
            fill(ctx)
        end
        if length(guesshistory) > 0
            move_to(ctx, 0, 155)
            set_source(ctx, workcolor)
            show_text(ctx, "Past Guesses: " * "-"^70)
            for (i, g) in enumerate(guesshistory), (j, c) in enumerate(tovec(g)[codelength+1:end])
                x = j * 40 + (j > codelength ? 20 : 0)
                y = 150 + 40 * i
                set_source(ctx, c)
                circle(ctx, x, y, 20)
                fill(ctx)
            end
        end
        Gtk.showall(win)
    end

    function submitguess!(w)
        if length(currentguess) == length(code)
            g = Guess(code, currentguess)
            push!(guesshistory, g)
            empty!(currentguess)
            guessesused += 1
            draw(can)
            if all(i -> g.code[i] == g.guess[i], 1:length(code))
                saywon!()
            elseif guessesused > maxguesses
                outofguesses!()
            end
        end
    end
    signal_connect(submitguess!, tryguess, :clicked)

    function undolast!(w)
        if length(currentguess) > 0
            pop!(currentguess)
            draw(can)
        end
    end
    signal_connect(undolast!, eraselast, :clicked)

    newgame!(win)
    Gtk.showall(win)

    condition = Condition()
    endit(w) = notify(condition)
    signal_connect(endit, win, :destroy)
    showall(win)
    wait(condition)
end

mastermindapp()


// version 1.2.51

import java.util.Random

val rand = Random()

class Mastermind {
    private val codeLen: Int
    private val colorsCnt: Int
    private var guessCnt = 0
    private val repeatClr: Boolean

    private val colors: String
    private var combo = ""

    private val guesses = mutableListOf<CharArray>()
    private val results = mutableListOf<CharArray>()

    constructor(codeLen: Int, colorsCnt: Int, guessCnt: Int, repeatClr: Boolean) {
        val color = "ABCDEFGHIJKLMNOPQRST"
        this.codeLen = codeLen.coerceIn(4, 10)
        var cl = colorsCnt
        if (!repeatClr && cl < this.codeLen) cl = this.codeLen
        this.colorsCnt = cl.coerceIn(2, 20)
        this.guessCnt = guessCnt.coerceIn(7, 20)
        this.repeatClr = repeatClr
        this.colors = color.take(this.colorsCnt)
    }

    fun play() {
        var win = false
        combo = getCombo()
        while (guessCnt != 0) {
            showBoard()
            if (checkInput(getInput())) {
                win = true
                break
            }
            guessCnt--
        }
        println("\n\n--------------------------------")
        if (win) {
            println("Very well done!\nYou found the code: $combo")
        }
        else {
            println("I am sorry, you couldn't make it!\nThe code was: $combo")
        }
        println("--------------------------------\n")
    }

    private fun showBoard() {
        for (x in 0 until guesses.size) {
            println("\n--------------------------------")
            print("${x + 1}: ")
            for (y in guesses[x]) print("$y ")
            print(" :  ")
            for (y in results[x]) print("$y ")
            val z = codeLen - results[x].size
            if (z > 0) print("- ".repeat(z))
        }
        println("\n")
    }

    private fun getInput(): String {
        while (true) {
            print("Enter your guess ($colors): ")
            val a = readLine()!!.toUpperCase().take(codeLen)
            if (a.all { it in colors } ) return a
        }
    }

    private fun checkInput(a: String): Boolean {
        guesses.add(a.toCharArray())
        var black = 0
        var white = 0
        val gmatch = BooleanArray(codeLen)
        val cmatch = BooleanArray(codeLen)
        for (i in 0 until codeLen) {
            if (a[i] == combo[i]) {
                gmatch[i] = true
                cmatch[i] = true
                black++
            }
        }
        for (i in 0 until codeLen) {
            if (gmatch[i]) continue
            for (j in 0 until codeLen) {
                if (i == j || cmatch[j]) continue
                if (a[i] == combo[j]) {
                    cmatch[j] = true
                    white++
                    break
                }
            }
        }
        val r = mutableListOf<Char>()
        r.addAll("X".repeat(black).toList())
        r.addAll("O".repeat(white).toList())
        results.add(r.toCharArray())
        return black == codeLen
    }

    private fun getCombo(): String {
        val c =  StringBuilder()
        val clr = StringBuilder(colors)
        for (s in 0 until codeLen) {
            val z = rand.nextInt(clr.length)
            c.append(clr[z])
            if (!repeatClr) clr.deleteCharAt(z)
        }
        return c.toString()
    }
}

fun main(args: Array<String>) {
    val m = Mastermind(4, 8, 12, false)
    m.play()
}


math.randomseed( os.time() )
local black, white, none, code = "X", "O", "-"
local colors, codeLen, maxGuess, rept, alpha, opt = 6, 4, 10, false, "ABCDEFGHIJKLMNOPQRST", ""
local guesses, results
function createCode()
    code = ""
    local dic, a = ""
    for i = 1, colors do
        dic = dic .. alpha:sub( i, i )
    end
    for i = 1, codeLen do
        a = math.floor( math.random( 1, #dic ) )
        code = code .. dic:sub( a, a )
        if not rept then
            dic = dic:sub(1, a - 1 ) .. dic:sub( a + 1, #dic )
        end
    end
end
function checkInput( inp )
    table.insert( guesses, inp )
    local b, w, fnd, str = 0, 0, {}, ""
    for bl = 1, codeLen do
        if inp:sub( bl, bl ) == code:sub( bl, bl ) then
            b = b + 1; fnd[bl] = true
        else
            for wh = 1, codeLen do
                if nil == fnd[bl] and wh ~= bl and inp:sub( wh, wh ) == code:sub( bl, bl ) then
                    w = w + 1; fnd[bl] = true
                end
            end
        end
    end
    for i = 1, b do str = str .. string.format( "%s ", black ) end
    for i = 1, w do str = str .. string.format( "%s ", white ) end
    for i = 1, 2 * codeLen - #str, 2 do str = str .. string.format( "%s ", none ) end
    table.insert( results, str )
    return b == codeLen
end
function play()
    local err, win, r = true, false;
    for j = 1, colors do opt = opt .. alpha:sub( j, j ) end
    while( true ) do
        createCode(); guesses, results = {}, {}
        for i = 1, maxGuess do
            err = true;
            while( err ) do
                io.write( string.format( "\n-------------------------------\nYour guess (%s)?", opt ) )
                inp = io.read():upper();
                if #inp == codeLen then
                    err = false;
                    for k = 1, #inp do
                        if( nil == opt:find( inp:sub( k, k ) ) ) then
                            err = true;
                            break;
                        end
                    end
                end
            end
            if( checkInput( inp ) ) then win = true; break
            else
                for l = 1, #guesses do
                    print( string.format( "%.2d: %s : %s", l, guesses[l], results[l] ) )
                end
            end
        end
        if win then print( "\nWell done!" )
        else print( string.format( "\nSorry, you did not crack the code --> %s!", code ) )
        end
        io.write( "Play again( Y/N )? " ); r = io.read()
        if r ~= "Y" and r ~= "y" then break end
    end
end
--[[ entry point ]]---
if arg[1] ~= nil and tonumber( arg[1] ) > 1 and tonumber( arg[1] ) < 21 then colors = tonumber( arg[1] ) end
if arg[2] ~= nil and tonumber( arg[2] ) > 3 and tonumber( arg[2] ) < 11 then codeLen = tonumber( arg[2] ) end
if arg[3] ~= nil and tonumber( arg[3] ) > 6 and tonumber( arg[3] ) < 21 then maxGuess = tonumber( arg[3] ) end
if arg[4] ~= nil and arg[4] == "true" or arg[4] == "false" then rept = ( arg[4] == "true" ) end
play()


import random, sequtils, strformat, strutils

proc encode(correct, guess: string): string =
  result.setlen(correct.len)
  for i in 0..correct.high:
    result[i] = if correct[i] == guess[i]: 'X' else: (if guess[i] in correct: 'O' else: '-')
  result.join(" ")

proc safeIntInput(prompt: string; minVal, maxVal: Positive): int =
  while true:
    stdout.write prompt
    let userInput = stdin.readLine()
    result = try: parseInt(userInput)
             except ValueError: continue
    if result in minVal..maxVal:
      return

proc playGame() =

  echo "You will need to guess a random code."
  echo "For each guess, you will receive a hint."
  echo "In this hint, X denotes a correct letter, " &
       "and O a letter in the original string but in a different position."
  echo ""

  let numLetters = safeIntInput("Select a number of possible letters for the code (2-20): ", 2, 20)
  let codeLength = safeIntInput("Select a length for the code (4-10): ", 4, 10)
  let letters = "ABCDEFGHIJKLMNOPQRST"[0..<numLetters]
  let code = newSeqWith(codeLength, letters.sample()).join()
  var guesses: seq[string]

  while true:
    echo ""
    stdout.write &"Enter a guess of length {codeLength} ({letters}): "
    let guess = stdin.readLine().toUpperAscii().strip()
    if guess.len != codeLength or guess.anyIt(it notin letters):
      continue
    elif guess == code:
      echo &"\nYour guess {guess} was correct!"
      break
    else:
      guesses.add &"{guesses.len + 1}: {guess.join(\" \")} => {encode(code, guess)}"
    for guess in guesses:
      echo "------------------------------------"
      echo guess
    echo "------------------------------------"

randomize()
playGame()


use List::Util qw(any);

print 'Enter pool size, puzzle size, attempts allowed: ';
($pool,$length,$tries) = split /\s+/, <>;
$length =  4 if $length eq '' or $length < 3 or $length > 11;
$pool   =  6 if $pool   eq '' or $pool   < 2 or $pool   > 21;
$tries  = 10 if $tries  eq '' or $tries  < 7 or $tries  > 21;

@valid  = sort { -1 + 2*int(rand 2) } ('A' .. 'T')[0..$pool-1];
@puzzle = @valid[0..$length-1];

$black = '●';
$white = '○';

while () {
    header();
    print "$_\n" for @guesses;
    lose() if  @guesses == $tries;
    @guess = get_guess();
    next unless is_valid(@guess);
    $score = score(\@puzzle, \@guess);
    win() if $score eq join ' ', ($black) x $length;
    push @guesses, join(' ', @guess) . ' :: ' . $score;
}

sub score {
    local *puzzle = shift;
    local *guess  = shift;
    my @score;
    for $i (0..$length-1) {
        if    (     $puzzle[$i] eq $guess[$i]) { push @score, $black }
        elsif (any {$puzzle[$i] eq $_} @guess) { push @score, $white }
        else                                   { push @score, '-'    }
    }
    join ' ', reverse sort @score;
}

sub header {
    $num = $tries - @guesses;
    print  "Valid letter, but wrong position: ○ - Correct letter and position: ●\n";
    print  "Guess the $length element sequence containing the letters " . join(', ', sort @valid) . "\n";
    printf "Repeats are not allowed. You have $num guess%s remaining\n", $num > 1 ? 'es' : '';
}

sub get_guess { print 'Your guess?: '; $g = <>; return split /\s*/, uc $g }

sub is_valid { $length == @_ }

sub win  { print 'You win! The correct answer is: ' . join(' ',@puzzle) . "\n"; exit }

sub lose { print 'Too bad, you ran out of guesses. The solution was: ' . join(' ',@puzzle) . "\n"; exit }


-- demo/rosetta/mastermind.exw
constant SET_LIMIT = 1_000_000  -- above this, it uses random sampling.

constant help_text = """
The game of mastermind, with a Knuth solver.

Specify the number of colours (1..20), the code length (1..10), the
number of guesses allowed (1-20), and whether colours can be repeated
(auto-ticked & greyed-out inactive when length>colours).

Note that at the highest settings there are 10,240,000,000,000 possible
answers: the (order n squared) analysis of that is simply not practical,
as indeed is simply building the initial list of all possible answers,
and therefore a fixed limit of 1,000,000 has been applied, which also
just about manages to keep the program responsive. Obviously, should the
actual answer not be among those samples, it cannot possibly find it,
and it will tell you in plain english when that occurs. You can always
trim the search space back to something more reasonable at any time, and
still play the game when that limit is breached, with weaker hints.

Conversely the lowest settings do not make for an interesting game, but
proved quite useful when ironing out some bugs, so were left in.

The Use button (disabled until something useful found) allows you to
take the best found (so far), displayed at the top of the colours frame.
Obviously "1/1 (100%)" means that it has deduced the correct answer.
Below that the colours frame shows all available colours, which can be
individually clicked in any order.

Press Delete or click on the last peg (in the left-hand game frame)
to remove it, before the last one is placed, however once full your
turn is immediately scored and cannot be undone.

New Game, Help, and Exit buttons are assumed to be self-explanatory.
Changing the option settings implicitly triggers a new game, except
for the number of permitted guesses, pre-game-over. Reducing the
number of guesses can also be used as a means of conceding.

When a correct guess is entered or all guesses have been used the hint
and colours are replaced with "GAME OVER - YOU WIN/LOSE" under the
actual answer.
"""
include pGUI.e

Ihandle dlg, colours, codelen, maxgoes, repeats, progress,
        usehint, game_canvas, colour_canvas
integer ncolours, ncodelen, nmaxgoes
bool brepeats

sequence secret = {},
         hint = {},
         guesses = {{}},
         scores = {}
--
-- note: while the game is ongoing, length(guesses) should always be
--       length(scores)+1; equal lengths is equivalent to game over.
--

function get_score(sequence guess, goal)
integer blacks = 0, -- (right colour & place)
        whites = 0  -- ("" but wrong place)
    for i=1 to length(guess) do
        if guess[i]=goal[i] then
            blacks += 1
            guess[i] = ' '
            goal[i] = ' '
        end if
    end for
    for i=1 to length(guess) do
        if guess[i]!=' ' then
            integer k = find(guess[i],goal)
            if k then
                whites += 1
                goal[k] = ' '
            end if
        end if
    end for
    return {blacks, whites}
end function

function random_set()
-- create the secret code, and/or (when rqd) a SET_LIMIT random sample
    sequence cset = tagset(ncolours),
             res = repeat(0,ncodelen)
    for i=1 to ncodelen do
        integer c = rand(length(cset))
        res[i] = cset[c]
        if not brepeats then
            cset[c..c] = {}
        end if
    end for
    return res
end function

sequence initial_set
atom is_len,    -- logically length(initial_set), except when > SET_LIMIT.
     excluded   -- initialset[1..excluded-1], are not, [excluded..$] are.

procedure create_initial_set()
    is_len = iff(brepeats?power(ncolours,ncodelen):k_perm(ncolours,ncodelen))
    if is_len<=SET_LIMIT then
        --
        -- generate the full set
        --
        initial_set = repeat(0,is_len)
        excluded = is_len+1 -- (ie none)
        sequence next = iff(brepeats?repeat(1,ncodelen):tagset(ncodelen))
        for i=1 to is_len do
            initial_set[i] = next
            for ndx=length(next) to 1 by -1 do
                integer n = next[ndx]
                while n<=ncolours do
                    n += 1
                    if brepeats
--                  or not find(n,next[1..ndx-1]) then
                    or not find(n,next) then --(see below)
                        exit
                    end if
                end while
                next[ndx] = n
                if n<=ncolours then
                    if not brepeats then
                        --
                        -- Fill in the rest lowest-first, eg
                        -- in the 4 colours and 4 holes case:
                        --   (start)       (above)       (this)
                        --  {1,2,3,4} --> {1,2,4,_} --> {1,2,4,3}
                        --  {1,2,4,3} --> {1,3,_,_} --> {1,3,2,4}
                        --  ...   (20 other cases omitted)
                        --  {4,3,1,2} --> {4,3,2,_} --> {4,3,2,1}
                        --
                        -- (probably sub-optimal, but insignificant
                        --  vs. the o(n^2) analysis which follows.)
                        --
                        for j=ndx+1 to length(next) do
                            for k=1 to ncolours do
--                              if not find(k,next[1..j-1]) then
                                if not find(k,next) then --(see below)
                                    next[j] = k
                                    exit
                                end if
                            end for
                        end for
                    end if
                    exit
                end if
                --
                -- technical note: if not brepeats, we are going to
                -- replace all next[ndx..$] later/above anyway, but
                -- replacing with 0 means we can avoid those slices.
                -- The next three all work: 1 is perfect for the
                -- brepeats=true case, but brepeats=false needs the
                -- above slices, while the 2nd & 3rd are equivalent
                -- the latter is obviously somewhat faster, at the
                -- cost of a wtf?!, without a comment such as this.
                --
--              next[ndx] = 1
--              next[ndx] = iff(brepeats?1:0)
                next[ndx] = brepeats -- (equivalent)
            end for
        end for
    else
        --
        -- generate SET_LIMIT random codes
        -- note that if (as is quite likely) the actual answer is
        -- not present in initial_set, then obviously it cannot
        -- possibly find it!
        --
        initial_set = repeat(0,SET_LIMIT)
        excluded = SET_LIMIT+1  -- (ie none)
        for i=1 to SET_LIMIT do
            initial_set[i] = random_set()
        end for
    end if
end procedure

atom done, is_done, best

function idle_action()
    atom to_do = excluded-1,
         t1 = time()+1
    string samp = iff(is_len=length(initial_set)?"":sprintf(" samples of %,d",{is_len}))
    for i=1 to 100000 do    -- reasonable slice of work
        done += 1
        is_done += (done<excluded)
        sequence guest = initial_set[done],
                 scores = {}, counts = {}
        if not find(guest,guesses) then
            for j=1 to excluded-1 do
                sequence s = get_score(guest,initial_set[j])
                integer k = find(s,scores)
                if k=0 then
                    scores = append(scores,s)
                    counts = append(counts,1)
                else
                    counts[k] += 1
                end if
            end for
            if length(counts)=0 then
                IupSetStrAttribute(progress,"TITLE","[answer not in %,d%s]",{SET_LIMIT,samp})
                return IUP_IGNORE   -- (stop idle)
            end if
            integer k = largest(counts,return_index:=true),
                    ck = counts[k]
            if ck<best then
                best = ck
                hint = guest
                IupSetInt(usehint,"ACTIVE",true)
                IupUpdate(colour_canvas)
            end if
        end if

        if done=length(initial_set) then
            IupSetStrAttribute(progress,"TITLE","%,d/%,d%s (100%%)",{is_done,to_do,samp})
            return IUP_IGNORE   -- (stop idle)
        end if
        if time()>t1 then exit end if
    end for
    IupSetStrAttribute(progress,"TITLE","%,d/%,d%s (%d%%)",{is_done,to_do,samp,100*(is_done/to_do)})
    return IUP_DEFAULT
end function
constant idle_action_cb = Icallback("idle_action")

procedure start_idle()
    done = 0
    is_done = 0
    best = length(initial_set)+1
    IupSetGlobalFunction("IDLE_ACTION",idle_action_cb)
end procedure

procedure new_game()
    ncolours = IupGetInt(colours,"VALUE")
    ncodelen = IupGetInt(codelen,"VALUE")
    nmaxgoes = IupGetInt(maxgoes,"VALUE")
    brepeats = IupGetInt(repeats,"VALUE")
    secret = random_set()
    guesses = {{}}
    scores = {}
    hint = {}
    create_initial_set()
    start_idle()
end procedure

constant colour_table = {#e6194b,   -- Red
                         #3cb44b,   -- Green
                         #ffe119,   -- Yellow
                         #4363d8,   -- Blue
                         #f58231,   -- Orange
                         #911eb4,   -- Purple
                         #42d4f4,   -- Cyan
                         #f032e6,   -- Magenta
                         #bfef45,   -- Lime
                         #fabebe,   -- Pink
                         #469990,   -- Teal
                         #e6beff,   -- Lavender
                         #9A6324,   -- Brown
                         #fffac8,   -- Beige
                         #800000,   -- Maroon
                         #aaffc3,   -- Mint
                         #808000,   -- Olive
                         #ffd8b1,   -- Apricot
                         #000075,   -- Navy
                         #a9a9a9}   -- Grey

-- saved in redraw_cb(), for click testing in button_cb():
sequence last_guess = {},
         colour_centres = {}
integer guess_r2 = 0,
        colour_r2 = 0

function redraw_cb(Ihandle ih, integer /*posx*/, integer /*posy*/)
    Ihandle frame = IupGetParent(ih)
    string title = IupGetAttribute(ih,"TITLE")
    if not find(title,{"Game","Colours"}) then ?9/0 end if
    integer {cw,ch} = IupGetIntInt(ih, "DRAWSIZE")

    cdCanvas cddbuffer = IupGetAttributePtr(ih,"DBUFFER")
    IupGLMakeCurrent(ih)
    cdCanvasActivate(cddbuffer)
    cdCanvasClear(cddbuffer)

    if title="Game" then
        integer mx = min(floor(cw/(ncodelen*1.5+0.5)),floor(ch/(nmaxgoes+1))),
                diameter = floor(mx/2),
                px = floor((cw-(ncodelen*1.5+0.5)*mx)/2), -- (set margin)
                cy = ch, cx, c, r
        last_guess = {}
        for g=1 to length(guesses) do
            cy -= mx
            cx = px+floor(mx/2)
            for i=1 to 2*ncodelen+1 do
                if i!=ncodelen+1 then
                    if i<=ncodelen then
                        if i<=length(guesses[g]) then
                            c = colour_table[guesses[g][i]]
                            if g=length(guesses) then
                                last_guess = {{cx,ch-cy}}
                            end if
                        else
                            c = CD_GREY
                        end if
                        r = diameter
                    else
                        c = CD_GREY
                        if g<=length(scores) then
                            integer k = i-ncodelen-1,
                                    {b,w} = scores[g]
                            c = iff(k<=b ? CD_BLACK : iff(k<=b+w ? CD_WHITE : CD_GREY))
                        end if
                        r = floor(diameter*0.5)
                    end if
                    cdCanvasSetForeground(cddbuffer,c)
                    cdCanvasSector(cddbuffer, cx, cy, r, r, 0, 360)
                    cdCanvasSetForeground(cddbuffer,CD_DARK_GREY)
                    cdCanvasCircle(cddbuffer, cx, cy, r)
                end if
                cx += iff(i<ncodelen?mx:floor(mx/2))
            end for
        end for
        guess_r2 = floor(diameter*diameter/4)
    elsif title="Colours" then
        integer mx = min(floor(cw/ncodelen),floor(ch/2)),
                r = floor(mx/2),
                px = floor((cw-ncodelen*mx)/2), -- (set margin)
                cy = ch-r, cx, c
        cx = px+floor(mx/2)
        bool active = length(hint)>0
        if length(scores)=nmaxgoes then
            hint = secret
            active = true
        end if
        for i=1 to ncodelen do
            c = iff(active?colour_table[hint[i]]:CD_GREY)
            cdCanvasSetForeground(cddbuffer,c)
            cdCanvasSector(cddbuffer, cx, cy, r, r, 0, 360)
            cdCanvasSetForeground(cddbuffer,CD_DARK_GREY)
            cdCanvasCircle(cddbuffer, cx, cy, r)
            cx += mx
        end for
        if length(scores)=nmaxgoes
        or guesses[$]=secret then
            ch -= floor(mx/2)
            {} = cdCanvasTextAlignment(cddbuffer, CD_CENTER)
            string wl = iff(guesses[$]=secret?"WIN":"LOSE"),
                   msg = sprintf("GAME OVER - YOU %s",{wl})
            cdCanvasText(cddbuffer, cw/2, ch/2, msg)
        else
            integer ch0 = ch
            ch -= mx
            --
            -- calculate the best nw*nh way to fit all the colours in:
            -- (if nw ends up = ncodelen there is no clear separation
            --  between the hint and the colour table; the start with
            --  ncodelen+1 solves that and looks pretty good to me.)
            --
            integer nw = ncodelen+1,    -- (as above)
                    nh = 1
            while nw*nh<ncolours do
                if (cw/(nw+1))>(ch/(nh+1)) then
                    nw += 1
                else
                    nh += 1
                end if
            end while
            --
            -- now draw all the colours
            --
            mx = min(floor(cw/nw),floor(ch/nh))
            r = floor(mx/2)
            px = floor((cw-nw*mx)/2)
            cx = px+floor(mx/2)
            cy = ch-r
            integer this_row = 0
            colour_centres = repeat(0,ncolours)
            colour_r2 = floor(r*r/4)
            for i=1 to ncolours do
                colour_centres[i] = {cx,ch0-cy}
                c = colour_table[i]
                cdCanvasSetForeground(cddbuffer,c)
                cdCanvasSector(cddbuffer, cx, cy, r, r, 0, 360)
                cdCanvasSetForeground(cddbuffer,CD_DARK_GREY)
                cdCanvasCircle(cddbuffer, cx, cy, r)
                cx += mx
                this_row += 1
                if this_row>=nw then
                    this_row = 0
                    cx = px + floor(mx/2)
                    cy -= mx
                end if
            end for
        end if
    end if
    cdCanvasFlush(cddbuffer)
    return IUP_DEFAULT
end function

function map_cb(Ihandle ih)
    IupGLMakeCurrent(ih)
    atom res = IupGetDouble(NULL, "SCREENDPI")/25.4
    cdCanvas cddbuffer = cdCreateCanvas(CD_GL, "10x10 %g", {res})
    IupSetAttributePtr(ih,"DBUFFER",cddbuffer)
    cdCanvasSetBackground(cddbuffer, CD_PARCHMENT)
    return IUP_DEFAULT
end function

function canvas_resize_cb(Ihandle canvas)
    cdCanvas cddbuffer = IupGetAttributePtr(canvas,"DBUFFER")
    integer {canvas_width, canvas_height} = IupGetIntInt(canvas, "DRAWSIZE")
    atom res = IupGetDouble(NULL, "SCREENDPI")/25.4
    cdCanvasSetAttribute(cddbuffer, "SIZE", "%dx%d %g", {canvas_width, canvas_height, res})
    return IUP_DEFAULT
end function

procedure redraw_all()
    IupUpdate({game_canvas,colour_canvas})
end procedure

procedure undo_move()
-- Called from button_cb and from K_DEL, but latter may be invalid.
    if length(guesses[$])!=0 then
        guesses[$] = guesses[$][1..$-1]
        redraw_all()
    end if
end procedure

procedure add_move(integer i)
    if i!=0 then
        guesses[$] &= i
    end if
    if length(guesses[$])=ncodelen then
        sequence guest = guesses[$],
                 score = get_score(guest,secret)
        scores = append(scores,score)
        if score!={ncodelen,0}  -- (not all black==game over)
        and length(guesses)<nmaxgoes then
            for i=excluded-1 to 1 by -1 do
                sequence isi = initial_set[i]
                if get_score(guest,isi)!=score then
                    excluded -= 1
                    if excluded!=i then
                        initial_set[i] = initial_set[excluded]
                        initial_set[excluded] = isi -- (swap)
                    end if
                end if
            end for
            guesses = append(guesses,{})
            hint = {}
            IupSetAttribute(progress,"TITLE","-")
            IupSetInt(usehint,"ACTIVE",false)
            start_idle()
        end if
    end if
    redraw_all()
end procedure

function usehint_cb(Ihandle /*usehint*/)
    guesses[$] = hint
    add_move(0)
    return IUP_DEFAULT
end function

function button_cb(Ihandle canvas, integer button, pressed, x, y, atom /*pStatus*/)
    Ihandle frame = IupGetParent(canvas)
    string title = IupGetAttribute(frame,"TITLE")
    if not find(title,{"Game","Colours"}) then ?9/0 end if
    if button=IUP_BUTTON1 and not pressed then      -- (left button released)
        {sequence centres, integer r2} = iff(title="Game"?{last_guess,guess_r2}
                                                         :{colour_centres,colour_r2})
        for i=1 to length(centres) do
            integer {cx,cy} = sq_sub(centres[i],{x,y})
            if (cx*cx+cy*cy)<=r2 then
                if title="Game" then
                    undo_move()
                else
                    add_move(i)
                end if
                exit
            end if
        end for
    end if
    return IUP_CONTINUE
end function

function new_game_cb(Ihandle /*ih*/)
    new_game()
    redraw_all()
    return IUP_DEFAULT
end function

function exit_cb(Ihandle /*ih*/)
    return IUP_CLOSE
end function
constant cb_exit = Icallback("exit_cb")

function help_cb(Ihandln /*ih*/)
    IupMessage("Mastermind",help_text)
    return IUP_DEFAULT
end function

function key_cb(Ihandle /*dlg*/, atom c)
    if c=K_ESC then return IUP_CLOSE end if
    if c=K_F1 then return help_cb(NULL) end if
    if find(c,{K_DEL,K_BS}) then undo_move() end if
    return IUP_CONTINUE
end function

function valuechanged_cb(Ihandle ih)
    ncolours = IupGetInt(colours,"VALUE")
    ncodelen = IupGetInt(codelen,"VALUE")
    nmaxgoes = IupGetInt(maxgoes,"VALUE")
    IupSetInt(repeats,"ACTIVE",ncodelen<=ncolours)
    if ncodelen>ncolours then
        IupSetInt(repeats,"VALUE",true)
    end if
    brepeats = IupGetInt(repeats,"VALUE")
    if ih!=maxgoes
    or length(scores)=length(guesses) then  -- (game over)
        new_game()
    elsif nmaxgoes<=length(scores) then
        -- (signal/force game over state)
        guesses = guesses[1..length(scores)]
    end if
    redraw_all()
    return IUP_DEFAULT
end function
constant cb_valuechanged = Icallback("valuechanged_cb")

procedure main()
    IupOpen()

    colours = IupText("SPIN=Yes, SPINMIN=1, SPINMAX=20, VALUE=6, RASTERSIZE=34x")
    codelen = IupText("SPIN=Yes, SPINMIN=1, SPINMAX=10, VALUE=4, RASTERSIZE=34x")
    maxgoes = IupText("SPIN=Yes, SPINMIN=1, SPINMAX=20, VALUE=7, RASTERSIZE=34x")
    repeats = IupToggle("Repeatable?","VALUE=YES, RIGHTBUTTON=YES, PADDING=5x4")
    progress = IupLabel("-","EXPAND=HORIZONTAL, PADDING=5x4")
    usehint = IupButton("Use",Icallback("usehint_cb"),"PADDING=5x4, ACTIVE=NO")
    game_canvas = IupGLCanvas("RASTERSIZE=200x")
    colour_canvas = IupGLCanvas("RASTERSIZE=x200")
    Ihandle newgame = IupButton("New Game",Icallback("new_game_cb"),"PADDING=5x4"),
            help = IupButton("Help (F1)",Icallback("help_cb"),"PADDING=5x4"),
            quit = IupButton("E&xit",Icallback("exit_cb"),"PADDING=5x4"),
            vbox = IupVbox({IupHbox({IupLabel("Colours (1-20)","PADDING=5x4"),colours}),
                            IupHbox({IupLabel("Code Length (1-10)","PADDING=5x4"),codelen}),
                            IupHbox({IupLabel("Guesses (1-20)","PADDING=5x4"),maxgoes}),
                            IupHbox({repeats},"MARGIN=10x5"),
                            IupHbox({progress}),
                            IupHbox({usehint,newgame,help,quit})},"MARGIN=5x5"),
            game_frame = IupFrame(IupHbox({game_canvas},"MARGIN=3x3"),"TITLE=Game"),
            option_frame = IupFrame(vbox,"TITLE=Options"),
            colour_frame = IupFrame(colour_canvas,"TITLE=Colours"),
            full = IupHbox({game_frame,IupVbox({option_frame,colour_frame})})
    IupSetCallbacks({colours,codelen,maxgoes,repeats}, {"VALUECHANGED_CB", cb_valuechanged})
    IupSetCallbacks({game_canvas,colour_canvas}, {"ACTION", Icallback("redraw_cb"),
                                                  "MAP_CB", Icallback("map_cb"),
                                                  "RESIZE_CB", Icallback("canvas_resize_cb"),
                                                  "BUTTON_CB", Icallback("button_cb")})
    dlg = IupDialog(IupHbox({full},"MARGIN=3x3"),"TITLE=Mastermind")
    IupSetCallback(dlg, "K_ANY", Icallback("key_cb"))
    IupSetAttributeHandle(dlg,"DEFAULTENTER", usehint)

    new_game()

    IupShowXY(dlg,IUP_CENTER,IUP_CENTER)
    IupSetAttribute(dlg, "RASTERSIZE", NULL)
    IupSetStrAttribute(dlg, "MINSIZE", IupGetAttribute(dlg,"RASTERSIZE"))
    IupMainLoop()
    IupClose()
end procedure
main()


mastermind :- mastermind(7, 4, 8, no_duplicates).

mastermind(Colours, Length, Guesses, Duplicates) :-
	between(2, 20, Colours),
	between(4, 10, Length),
	between(7, 20, Guesses),
	member(Duplicates, [allow_duplicates, no_duplicates]),

	create_board(Colours, Length, Duplicates, Board),
	intro(Colours, Length, Duplicates),
	play(board(Board, Length, Colours, Guesses), [], 0), !.

intro(Colours, Length, Duplicates) :-
	format('Guess the code!~n'),
	format('There are ~p character types, and ~p letters to guess~n',
	    [Colours, Length]),
	Duplicates = allow_duplicates
	-> format('Duplicates are allowed~n~n')
	; format('Duplicates are not allowed~n~n').

/* Create the combination to be guessed */
create_board(Colours, Length, Duplicates, Board) :-
	length(Board, Length),
	valid_char_list(Colours, CharSet),
	repeat,
	maplist(random_alpha(CharSet), Board),
	check_for_duplicates(Board, Duplicates).

check_for_duplicates(_, allow_dupicates).
check_for_duplicates(Board, no_duplicates) :- is_set(Board).

/* Main loop - get the player guess and print out status */
play(board(Board,_,_,MaxGuesses), _, MaxGuesses) :-
	write('Sorry, You failed to guess in time...!\nThe code was : '),
	maplist(write, Board),
	nl.
play(BoardData, PrevGuesses, GuessNum) :-
	BoardData = board(_, Length, Colours, MaxGuesses),
	GuessNum < MaxGuesses,
	ReportedGuess is GuessNum + 1,
	format('Guess #~p of #~p: ', [ReportedGuess, MaxGuesses]),
	get_player_guess(Length, Colours, Guess),
	evaluate_and_print_result(BoardData, PrevGuesses, ReportedGuess, Guess).

evaluate_and_print_result(board(Board,_,_,_), _, _,Board) :-
	format('Well done! You Guessed Correctly.~n').
evaluate_and_print_result(BoardData, PrevGuesses, NextGuessNum, Guess) :-
	BoardData = board(Board, _, _, _),
	dif(Board, Guess),
	match_guess_to_board(Board, Guess, Diffs),
	append(PrevGuesses, [guess(NextGuessNum, Guess, Diffs)], Guesses),
	maplist(print_guess, Guesses),
	play(BoardData, Guesses, NextGuessNum).

/* Get the player guess and validate that it matches the rules */
get_player_guess(Length, Colours, Guess) :-
	repeat,
	read_line_to_string(user_input, Line),
	string_chars(Line, Guess),

	% validate the correct number of items have been entered
	length(Guess, Length),

	% validate that all the characters are valid for the number of colours
	valid_char_list(Colours, ValidCharSet),
	subset(Guess, ValidCharSet).

/* Predicates to figure out how many places are correct */
match_guess_to_board(Board, Guess, Matches) :-
	maplist(guess_differences(Board), Board, Guess, Differences),
	sort(0, @>=, Differences, Matches).

% Same position, same type
guess_differences(_Board, B, B, 'X').
% Same type, different position
guess_differences(Board, B, G, 'O') :- dif(B, G), member(G, Board).
% Type not on board
guess_differences(Board, B, G, '-') :- dif(B, G), \+ member(G, Board).

/* Print out the current progress */
print_guess(guess(NextGuessNumber, Guess, Differences))	:-
	format('~w: ', NextGuessNumber),
	maplist(format('~w '), Guess),
	format(' : '),
	maplist(format('~w '), Differences),
	nl.

/* Utils */
alpha_chars([a,b,c,d,e,f,g,h,i,j,k,l,m,n,o,p,q,r,s,t]).

valid_char_list(Colours, CharSet) :-
	alpha_chars(AllChars),
	truncate_list(AllChars, Colours, CharSet).

random_alpha(AllChars, RandomMember) :- random_member(RandomMember, AllChars).

truncate_list(_, 0, []).
truncate_list([A|T], N, [A|R]) :-
	N > 0,
	N1 is N - 1,
	truncate_list(T, N1, R).


import random

def encode(correct, guess):
    output_arr = [''] * len(correct)

    for i, (correct_char, guess_char) in enumerate(zip(correct, guess)):
        output_arr[i] = 'X' if guess_char == correct_char else 'O' if guess_char in correct else '-'

    return ''.join(output_arr)

def safe_int_input(prompt, min_val, max_val):
    while True:
        user_input = input(prompt)

        try:
            user_input = int(user_input)
        except ValueError:
            continue

        if min_val <= user_input <= max_val:
            return user_input

def play_game():
    print("Welcome to Mastermind.")
    print("You will need to guess a random code.")
    print("For each guess, you will receive a hint.")
    print("In this hint, X denotes a correct letter, and O a letter in the original string but in a different position.")
    print()

    number_of_letters = safe_int_input("Select a number of possible letters for the code (2-20): ", 2, 20)
    code_length = safe_int_input("Select a length for the code (4-10): ", 4, 10)

    letters = 'ABCDEFGHIJKLMNOPQRST'[:number_of_letters]
    code = ''.join(random.choices(letters, k=code_length))
    guesses = []

    while True:
        print()
        guess = input(f"Enter a guess of length {code_length} ({letters}): ").upper().strip()

        if len(guess) != code_length or any([char not in letters for char in guess]):
            continue
        elif guess == code:
            print(f"\nYour guess {guess} was correct!")
            break
        else:
            guesses.append(f"{len(guesses)+1}: {' '.join(guess)} => {' '.join(encode(code, guess))}")

        for i_guess in guesses:
            print("------------------------------------")
            print(i_guess)
        print("------------------------------------")

if __name__ == '__main__':
    play_game()


sub MAIN (
    Int :$colors  where 1 < * < 21 = 6,  Int :$length  where 3 < * < 11 = 4,
    Int :$guesses where 7 < * < 21 = 10, Bool :$repeat = False
  ) {
    my @valid = ('A' .. 'T')[^$colors];
    my $puzzle = $repeat ?? @valid.roll($length) !! @valid.pick($length);
    my @guesses;

    my $black = '●';
    my $white = '○';

    loop {
        clearscr();
        say header();
        printf " %{$length * 2}s :: %s\n", @guesses[$_][0], @guesses[$_][1] for ^@guesses;
        say '';
        lose() if @guesses == $guesses;
        my $guess = get-guess();
        next unless $guess.&is-valid;
        my $score = score($puzzle, $guess);
        win() if $score eq ($black xx $length).join: ' ';
        @guesses.push: [$guess, $score];
    }

    sub header {
        my $num = $guesses - @guesses;
        qq:to/END/;
        Valid letter, but wrong position: ○ - Correct letter and position: ●
        Guess the {$length} element sequence containing the letters {@valid}
        Repeats are {$repeat ?? '' !! 'not '}allowed. You have $num guess{ $num == 1 ?? '' !! 'es'} remaining.
        END
    }

    sub score ($puzzle, $guess) {
        my @score;
        for ^$length {
            if $puzzle[$_] eq $guess[$_] {
                @score.push: $black;
            }
            elsif $puzzle[$_] eq any(@$guess) {
                @score.push: $white;
            }
            else {
                @score.push('-');
            }
        }
        @score.sort.reverse.join: ' ';
    }

    sub clearscr { $*KERNEL ~~ /'win32'/ ?? run('cls') !! run('clear') }

    sub get-guess { (uc prompt 'Your guess?: ').comb(/@valid/) }

    sub is-valid (@guess) { so $length == @guess }

    sub win  { say 'You Win! The correct answer is: ', $puzzle; exit }

    sub lose { say 'Too bad, you ran out of guesses. The solution was: ', $puzzle; exit }
}


/*REXX pgm scores  mastermind  game with a human  or  CBLFs (Carbon Based Life Forms).  */
parse arg let wid mxG oRep seed _                /*obtain optional arguments from the CL*/
      arg  .   .   .   rep .                     /*get uppercase 4th argument  "   "  " */
if let=='' | let==","  then let= 20              /*Not specified?  Then use the default.*/
if wid=='' | wid==","  then wid=  4              /* "      "         "   "   "     "    */
if mxG=='' | mxG==","  then mxG= 20              /* "      "         "   "   "     "    */
if rep=='' | rep==","  then rep=  0              /* "      "         "   "   "     "    */
if datatype(seed,'W')  then call random ,,seed   /*use a seed for random repeatability. */
if abbrev(  'REPEATSALLOWED',rep,3)  then rep=1  /*allow an abbreviated option for REP. */
if abbrev('NOREPEATSALLOWED',rep,3)  then rep=0  /*  "    "      "         "    "   "   */
call vet   arg(),     'args'                     /*Vet the number of arguments entered. */  /*◄■■■■■■ optional vetting.*/
call vet     let,  'letters', 2, 20              /* "   "     "    " letters in the code*/  /*◄■■■■■■ optional vetting.*/
call vet     wid,    'width', 4, 10              /* "   "     "    " the width of code. */  /*◄■■■■■■ optional vetting.*/
call vet     mxG, 'maxGuess', 7, 20              /* "   "     "    " maximum guesses.   */  /*◄■■■■■■ optional vetting.*/
call vet     rep,      'REP', 0, 1e8             /* "   "   value if repeats are allowed*/  /*◄■■■■■■ optional vetting.*/
call gen;                                        yourG= 'Your guess must be exactly '
                                                 youve= "You've already tried that guess "
        do prompt=0  by 0  until xx==wid;   say  /*play until guessed or QUIT is entered*/
        say id 'Please enter a guess with '   wid  ' letters                   [or Quit]:'
        pull g;   g=space(g,0);  L=length(g);     if abbrev('QUIT',g,1)  then exit 0
        if L\==wid  then do;  say id '***error***'  yourG wid  " letters.";  iterate;  end
        call dups                                /*look through the history log for dups*/
        q=?;      XX=0;      OO=0;     try=try+1 /*initialize some REXX vars;  bump TRY.*/

             do j=1  for L;  if substr(g,j,1) \== substr(q,j,1)  then iterate    /*hit? */
             xx=xx+1;    q=overlay('▒', q, j)    /*bump the  XX  correct   count.       */
             end   /*j*/                         /* [↑]  XX  correct count; scrub guess.*/

             do k=1  for L;   _=substr(g, k, 1)  /*process the count for  "spots".      */
             if pos(_, q)==0  then iterate       /*is this  (spot)  letter in the code? */
             oo=oo+1;       q=translate(q, , _)  /*bump the  OO  spot count.            */
             end   /*k*/                         /* [↑]  OO  spot count;  & scrub guess.*/
        say
        @.try=id  right('guess'  try, 11)     '  ('mxG       "is the max):"    g   '──►' ,
                                      copies('X', xx)copies("O", oo)copies('-', wid-xx-oo)
        call hist
        if try==mxG  then do;  say;      say id   "you've used the maximum guesses:"   mxG
                               say;      say id   "The code was: "   ?;    say;     exit 1
                          end
        end   /*prompt*/
say;                           say "          ┌─────────────────────────────────────────┐"
                               say "          │                                         │"
                               say "          │  Congratulations, you've guessed it !!  │"
                               say "          │                                         │"
                               say "          └─────────────────────────────────────────┘"
exit 0                                           /*stick a fork in it,  we're all done. */
/*──────────────────────────────────────────────────────────────────────────────────────*/
dups:    do h=1  for try;  if g\=word(@.h, 8)  then iterate   /*any duplicated guesses? */
         say;  say id youve  " (guess number" h').'; iterate prompt; end  /*h*/;    return
/*──────────────────────────────────────────────────────────────────────────────────────*/
gen:  if rep==0  then reps= 'no'                 /*create a literal for the prompt msg. */
                 else reps=
      @abc= 'QWERTYUIOPASDFGHJKLZXCVBNM'         /*capital letters used for random code.*/
      id='────────';  try=0;  [email protected]=length(@abc) /*identifier in front of msg from here.*/
      ?=
          do  until  length(?)==wid              /*gen random codes 'til there's enough.*/
          r=substr(@abc, random(1, [email protected]), 1)    /*generate a random letter, 1 at a time*/
          if \rep & pos(r, ?)\==0  then iterate  /*maybe  don't  allow a repeated digit.*/
          ?=? || r; if ?=='QUIT'&let==4  then ?= /*append random letter; ··· except this*/
          end   /*until*/                        /* [↑]  builds a unique  N-letter code.*/
      say
      say id 'A random code of '   wid   "letters  (out of a possible "  let  ' letters) '
      say id 'has been generated   (with'    reps    "repeats)."
      return
/*──────────────────────────────────────────────────────────────────────────────────────*/
hist:    do hist=1  for try;  say @.hist;  end;   return         /*show "guess" history.*/
s:    if arg(1)==1  then return '';      return "s"              /*a simpler pluraizer. */
ser:  say;  say;    say '***error***'   arg(1);     say;   say;         exit 13
/*──────────────────────────────────────────────────────────────────────────────────────*/  /*◄■■■■■■ optional vetting.*/
vet:  parse arg val,?,mn,mx                      /*vet (validate) a specified argument. */  /*◄■■■■■■ optional vetting.*/
      if ?=="args" & (val>1 | _\='')  then call ser "Too many arguments specified. "  _     /*◄■■■■■■ optional vetting.*/
      if ?=="args"       then return                                                        /*◄■■■■■■ optional vetting.*/
      if \datatype(val, 'N')          then call ser ? "isn't numeric: "               val   /*◄■■■■■■ optional vetting.*/
      if \datatype(val, 'W')          then call ser ? "isn't an integer: "            val   /*◄■■■■■■ optional vetting.*/
      if val < mn                     then call ser ? "has a value less than "        mn    /*◄■■■■■■ optional vetting.*/
      if val > mx                     then call ser ? "has a value greater than "     mx    /*◄■■■■■■ optional vetting.*/
      if ?=='REP' & \datatype(val,W)  then call ser "Value for REPEATS isn't valid: " oRep  /*◄■■■■■■ optional vetting.*/
      return 1


# Project : Mastermind

colors = ["A", "B", "C", "D"]
places = list(2)
mind = list(len(colors))
rands = list(len(colors))
master = list(len(colors))
test = list(len(colors))
guesses  = 7
repeat = false
nr = 0

if repeat
   for n = 1 to len(colors)
        while true
                  rnd = random(len(colors)-1) + 1
                  if rands[rnd] != 1
                     mind[n] = rnd
                     rands[rnd] = 1
                    exit
                  ok
        end
   next
else
   for n = 1 to len(colors)
        rnd = random(len(colors)-1) + 1
        mind[n] = rnd
    next
ok

for n = 1 to len(colors)
      master[n] = char(64+mind[n])
next
while true
         for p = 1 to len(places)
               places[p] = 0
         next
         nr = nr + 1
         see "Your guess (ABCD)? "
         give testbegin
         for d = 1 to len(test)
               test[d] = testbegin[d]
         next
         flag = 1
         for n = 1 to len(test)
               if upper(test[n]) != master[n]
                  flag = 0
               ok
         next
         if flag = 1
            exit
         else
            for x = 1 to len(master)
                  if upper(test[x]) = master[x]
                      places[1] = places[1] + 1
                  ok
            next
            mastertemp = master
            for p = 1 to len(test)
                  pos = find(mastertemp, upper(test[p]))
                  if pos > 0
                     del(mastertemp, pos)
                     places[2] = places[2] + 1
                  ok
            next
         ok
         place1 = places[1]
         place2 = places[2] - place1
         place3 = len(master) - (place1 + place2)
         showresult(test, place1, place2, place3)
         if nr = guesses
            exit
         ok
end
see "Well done!" + nl
see "End of game" + nl

func showresult(test, place1, place2, place3)
        see "" + nr + " : "
        for r = 1 to len(test)
             see test[r]
        next
        see " : "
        for n1 = 1 to place1
              see "X" + " "
        next
        for n2 = 1 to place2
              see "O" + " "
        next
        for n3 = 1 to place3
              see "-" + " "
        next
        see nl


extern crate rand;

use rand::prelude::*;
use std::io;

fn main() {
    let mut input_line = String::new();
    let colors_n;
    let code_len;
    let guesses_max;
    let colors_dup;

    loop {
        println!("Please enter the number of colors to be used in the game (2 - 20): ");
        input_line.clear();
        io::stdin()
            .read_line(&mut input_line)
            .expect("The read line failed.");
        match (input_line.trim()).parse::<i32>() {
            Ok(n) => {
                if n >= 2 && n <= 20 {
                    colors_n = n;
                    break;
                } else {
                    println!("Outside of range (2 - 20).");
                }
            }
            Err(_) => println!("Invalid input."),
        }
    }
    let colors = &"ABCDEFGHIJKLMNOPQRST"[..colors_n as usize];

    println!("Playing with colors {}.\n", colors);

    loop {
        println!("Are duplicated colors allowed in the code? (Y/N): ");
        input_line.clear();
        io::stdin()
            .read_line(&mut input_line)
            .expect("The read line failed.");
        if ["Y", "N"].contains(&&input_line.trim().to_uppercase()[..]) {
            colors_dup = input_line.trim().to_uppercase() == "Y";
            break;
        } else {
            println!("Invalid input.");
        }
    }
    println!(
        "Duplicated colors {}allowed.\n",
        if colors_dup { "" } else { "not " }
    );
    loop {
        let min_len = if colors_dup { 4 } else { 4.min(colors_n) };
        let max_len = if colors_dup { 10 } else { 10.min(colors_n) };
        println!(
            "Please enter the length of the code ({} - {}): ",
            min_len, max_len
        );
        input_line.clear();
        io::stdin()
            .read_line(&mut input_line)
            .expect("The read line failed.");
        match (input_line.trim()).parse::<i32>() {
            Ok(n) => {
                if n >= min_len && n <= max_len {
                    code_len = n;
                    break;
                } else {
                    println!("Outside of range ({} - {}).", min_len, max_len);
                }
            }
            Err(_) => println!("Invalid input."),
        }
    }
    println!("Code of length {}.\n", code_len);
    loop {
        println!("Please enter the number of guesses allowed (7 - 20): ");
        input_line.clear();
        io::stdin()
            .read_line(&mut input_line)
            .expect("The read line failed.");
        match (input_line.trim()).parse::<i32>() {
            Ok(n) => {
                if n >= 7 && n <= 20 {
                    guesses_max = n;
                    break;
                } else {
                    println!("Outside of range (7 - 20).");
                }
            }
            Err(_) => println!("Invalid input."),
        }
    }
    println!("{} guesses allowed.\n", guesses_max);

    let mut rng = rand::thread_rng();
    let mut code;
    if colors_dup {
        code = (0..code_len)
            .map(|_| ((65 + rng.gen_range(0, colors_n) as u8) as char))
            .collect::<Vec<_>>();
    } else {
        code = colors.chars().collect::<Vec<_>>();
        code.shuffle(&mut rng);
        code = code[..code_len as usize].to_vec();
    }
    //code = vec!['J', 'A', 'R', 'D', 'A', 'N', 'I'];
    //println!("Secret code: {:?}", code);
    let mut guesses: Vec<(String, String)> = vec![];
    let mut i = 1;
    loop {
        println!("Your guess ({}/{})?: ", i, guesses_max);
        input_line.clear();
        io::stdin()
            .read_line(&mut input_line)
            .expect("The read line failed.");
        let mut guess = input_line.trim().to_uppercase();
        if guess.len() as i32 > code_len {
            guess = guess[..code_len as usize].to_string();
        }
        let guess_v = guess.chars().collect::<Vec<char>>();
        let res = evaluate(&code, &guess_v);
        guesses.push((guess, res.clone()));
        let width = 8 + guesses_max.to_string().len() + code_len as usize * 2;
        println!("{}", "-".repeat(width));
        for (i, guess) in guesses.iter().enumerate() {
            let line = format!(
                " {:w1$} : {:w2$} : {:w2$} ",
                i + 1,
                guess.0,
                guess.1,
                w1 = guesses_max.to_string().len(),
                w2 = code_len as usize
            );
            println!("{}", line);
        }
        println!("{}", "-".repeat(width));
        if res == "X".repeat(code_len as usize) {
            println!("You won! Code: {}", code.into_iter().collect::<String>());
            break;
        }
        i += 1;
        if i > guesses_max {
            println!("You lost. Code: {}", code.into_iter().collect::<String>());
            break;
        }
    }
}

fn evaluate(code: &[char], guess: &[char]) -> String {
    let mut res: Vec<char> = vec![];
    for i in 0..guess.len() {
        if guess[i] == code[i] {
            res.push('X');
        } else if code.contains(&guess[i]) {
            res.push('O');
        } else {
            res.push('-');
        }
    }
    res.sort_by(|a, b| b.cmp(a));
    res.into_iter().collect()
}


    -- Create Table
    -- Distinct combination
    --- R :Red, B :Blue, G: Green, V: Violet, O: Orange, Y: Yellow
    DROP TYPE IF EXISTS color cascade;CREATE TYPE color AS ENUM ('R', 'B', 'G', 'V', 'O', 'Y');

    DROP TABLE IF EXISTS guesses cascade ; CREATE TABLE guesses (
        FIRST color,
        SECOND color,
        third color ,
        fourth color
    );
    CREATE TABLE mastermind () inherits (guesses);

    INSERT INTO mastermind VALUES ('G', 'B', 'R', 'V');

    INSERT INTO guesses VALUES ('Y', 'Y', 'B', 'B');
    INSERT INTO guesses VALUES ('V', 'R', 'R', 'Y');
    INSERT INTO guesses VALUES ('G', 'V', 'G', 'Y');
    INSERT INTO guesses VALUES ('R', 'R', 'V', 'Y');
    INSERT INTO guesses VALUES ('B', 'R', 'G', 'V');
    INSERT INTO guesses VALUES ('G', 'B', 'R', 'V');

    --- Matches Black
    CREATE OR REPLACE FUNCTION check_black(guesses,  mastermind) RETURNS INTEGER AS $$
        SELECT (
            ($1.FIRST  = $2.FIRST)::INT +
            ($1.SECOND  = $2.SECOND)::INT +
            ($1.third = $2.third)::INT +
            ($1.fourth = $2.fourth)::INT
        );
    $$ LANGUAGE SQL;

    --- Matches White
    CREATE OR REPLACE FUNCTION check_white(guesses,  mastermind) RETURNS INTEGER AS $$
        SELECT (
            CASE WHEN ($1.FIRST = $2.FIRST) THEN 0 ELSE 0 END +
            CASE WHEN ($1.SECOND = $2.SECOND) THEN 0 ELSE 0 END +
            CASE WHEN ($1.third = $2.third)  THEN 0 ELSE 0 END +
            CASE WHEN ($1.fourth = $2.fourth) THEN 0 ELSE 0 END +
            CASE WHEN ($1.FIRST != $2.FIRST) THEN (
                    $1.FIRST = $2.SECOND OR
                    $1.FIRST = $2.third OR
                    $1.FIRST = $2.fourth
                    )::INT ELSE 0 END +
            CASE WHEN ($1.SECOND != $2.SECOND) THEN (
                    $1.SECOND = $2.FIRST OR
                    $1.SECOND = $2.third OR
                    $1.SECOND = $2.fourth
                    )::INT ELSE 0 END +
            CASE WHEN ($1.third != $2.third) THEN (
                    $1.third = $2.FIRST OR
                    $1.third = $2.SECOND OR
                    $1.third = $2.fourth
                    )::INT ELSE 0 END +
            CASE WHEN ($1.fourth != $2.fourth) THEN (
                    $1.fourth = $2.FIRST OR
                    $1.fourth = $2.SECOND OR
                    $1.fourth = $2.third
                    )::INT ELSE 0 END
        ) FROM guesses
    $$ LANGUAGE SQL;

    SELECT guesses,
           check_black(guesses.*, mastermind.*),
           check_white(guesses.*, mastermind.*)
        FROM   guesses, mastermind


import "random" for Random
import "/ioutil" for Input
import "/str" for Str

var Rand = Random.new()

class Mastermind {
    construct new(codeLen, colorsCnt, guessCnt, repeatClr) {
        var color = "ABCDEFGHIJKLMNOPQRST"
        _codeLen = codeLen.clamp(4, 10)
        var cl = colorsCnt
        if (!repeatClr && cl < _codeLen) cl = _codeLen
        _colorsCnt = cl.clamp(2, 20)
        _guessCnt = guessCnt.clamp(7, 20)
        _repeatClr = repeatClr
        _colors = color.take(_colorsCnt).join()
        _combo = ""
        _guesses = []
        _results = []
    }

    play() {
        var win = false
        _combo = getCombo_()
        while (_guessCnt != 0) {
            showBoard_()
            if (checkInput_(getInput_())) {
                win = true
                break
            }
            _guessCnt = _guessCnt - 1
        }
        System.print("\n\n--------------------------------")
        if (win) {
            System.print("Very well done!\nYou found the code: %(_combo)")
        } else {
            System.print("I am sorry, you couldn't make it!\nThe code was: %(_combo)")
        }
        System.print("--------------------------------\n")
    }

    showBoard_() {
        for (x in 0..._guesses.count) {
            System.print("\n--------------------------------")
            System.write("%(x + 1): ")
            for (y in _guesses[x]) System.write("%(y) ")
            System.write(" :  ")
            for (y in _results[x]) System.write("%(y) ")
            var z = _codeLen - _results[x].count
            if (z > 0) System.write("- " * z)
        }
        System.print("\n")
    }

    getInput_() {
        while (true) {
            var a = Str.upper(Input.text("Enter your guess (%(_colors)): ", 1)).take(_codeLen)
            if (a.all { |c| _colors.contains(c) } ) return a.join()
        }
    }

    checkInput_(a) {
        _guesses.add(a.toList)
        var black = 0
        var white = 0
        var gmatch = List.filled(_codeLen, false)
        var cmatch = List.filled(_codeLen, false)
        for (i in 0..._codeLen) {
            if (a[i] == _combo[i]) {
                gmatch[i] = true
                cmatch[i] = true
                black = black + 1
            }
        }
        for (i in 0..._codeLen) {
            if (gmatch[i]) continue
            for (j in 0..._codeLen) {
                if (i == j || cmatch[j]) continue
                if (a[i] == _combo[j]) {
                    cmatch[j] = true
                    white = white + 1
                    break
                }
            }
        }
        var r = []
        r.addAll(("X" * black).toList)
        r.addAll(("O" * white).toList)
        _results.add(r)
        return black == _codeLen
    }

    getCombo_() {
        var c =  ""
        var clr = _colors
        for (s in 0..._codeLen) {
            var z = Rand.int(clr.count)
            c = c + clr[z]
            if (!_repeatClr) Str.delete(clr, z)
        }
        return c
    }
}

var m = Mastermind.new(4, 8, 12, false)
m.play()


class MasterMind{
   fcn init(code_len,guess_count){
      var codeLen =code_len.max(4).min(10);
      var guessCnt=guess_count.max(7).min(20);
      var colors  ="ABCDEFGHIJKLMNOPQRST"[0,codeLen];
   }
   fcn play{
      guesses,win,blackWhite:=List(),False,Void;
      code:=codeLen.pump(String,'wrap(_){ colors[(0).random(codeLen)] });
      do(guessCnt){
	 str:=getInput();
	 win,blackWhite = checkInput(str,code);
	 guesses.append(T(str,blackWhite));
	 showBoard(guesses);
	 if(win) break;
      }
      if(win) println("--------------------------------\n",
		"Very well done!\nYou found the code: ",code);
       else println("--------------------------------\n",
		"I am sorry, you didn't discover the code!\nThe code was: ",code);
    }
    fcn [private] showBoard(guesses){
       foreach n,gbw in ([1..].zip(guesses)){
          guess,blackWhite := gbw;
          println("%2d: %s :% s %s".fmt(n,
	     guess.split("").concat(" "), blackWhite.split("").concat(" "),
	     "- "*(codeLen - blackWhite.len())));
       }
    }
    fcn [private] getInput{
       while(True){
	  a:=ask("Enter your guess (" + colors + "): ").toUpper()[0,codeLen];
	  if(not (a-colors) and a.len()>=codeLen) return(a);
       }
    }
    fcn [private] checkInput(guess,code){
	// black: guess is correct in both color and position
        // white: correct color, wrong position
	matched,black := guess.split("").zipWith('==,code), matched.sum(0);
	// remove black from code, prepend null to make counting easy
	code  = L("-").extend(matched.zipWith('wrap(m,peg){ m and "-" or peg },code));
	white:=0; foreach m,p in (matched.zip(guess)){
	   if(not m and (z:=code.find(p))){ white+=1; code[z]="-"; }
	}
	return(black==codeLen,"X"*black + "O"*white)
    }
}(4,12).play();


F make_maze(w = 16, h = 8)
   V vis = [[0] * w [+] [1]] * h [+] [[1] * (w + 1)]
   V ver = [[‘|  ’] * w [+] [String(‘|’)]] * h [+] [[String]()]
   V hor = [[‘+--’] * w [+] [String(‘+’)]] * (h + 1)

   F walk(Int x, Int y) -> N
      @vis[y][x] = 1
      V d = [(x - 1, y), (x, y + 1), (x + 1, y), (x, y - 1)]
      random:shuffle(&d)
      L(=xx, =yy) d
         I yy == -1
            yy = @vis.len - 1
         I xx == -1
            xx = @vis[0].len - 1
         I @vis[yy][xx]
            L.continue
         I xx == x
            @hor[max(y, yy)][x] = ‘+  ’
         I yy == y
            @ver[y][max(x, xx)] = ‘   ’
         @walk(xx, yy)

   walk(random:(w), random:(h))

   V s = ‘’
   L(a, b) zip(hor, ver)
      s ‘’= (a [+] [String("\n")] + b [+] [String("\n")]).join(‘’)
   R s

print(make_maze())


DEFINE TOP="0"
DEFINE RIGHT="1"
DEFINE BOTTOM="2"
DEFINE LEFT="3"
DEFINE WIDTH="160"
DEFINE HEIGHT="96"

DEFINE STACK_SIZE="5000"
BYTE ARRAY stack(STACK_SIZE)
INT stackSize

PROC InitStack()
  stackSize=0
RETURN

BYTE FUNC IsEmpty()
  IF stackSize=0 THEN
    RETURN (1)
  FI
RETURN (0)

BYTE FUNC IsFull()
  IF stackSize>=STACK_SIZE THEN
    RETURN (1)
  FI
RETURN (0)

PROC Push(BYTE x,y)
  IF IsFull() THEN Break() RETURN FI
  stack(stackSize)=x stackSize==+1
  stack(stackSize)=y stackSize==+1
RETURN

PROC Pop(BYTE POINTER x,y)
  IF IsEmpty() THEN Break() RETURN FI
  stackSize==-1 y^=stack(stackSize)
  stackSize==-1 x^=stack(stackSize)
RETURN

PROC FillScreen()
  BYTE POINTER ptr ;pointer to the screen memory
  INT screenSize=[3840]

  ptr=PeekC(88)
  SetBlock(ptr,screenSize,$55)

  Color=0
  Plot(0,HEIGHT-1) DrawTo(WIDTH-1,HEIGHT-1) DrawTo(WIDTH-1,0)
RETURN

PROC GetNeighbors(BYTE x,y BYTE ARRAY n BYTE POINTER count)
  DEFINE WALL="1"

  count^=0
  IF y>2 AND Locate(x,y-2)=WALL THEN
    n(count^)=TOP count^==+1
  FI
  IF x<WIDTH-3 AND Locate(x+2,y)=WALL THEN
    n(count^)=RIGHT count^==+1
  FI
  IF y<HEIGHT-3 AND Locate(x,y+2)=WALL THEN
    n(count^)=BOTTOM count^==+1
  FI
  IF x>2 AND Locate(x-2,y)=WALL THEN
    n(count^)=LEFT count^==+1
  FI
RETURN

PROC Maze(BYTE x,y)
  BYTE ARRAY stack,neighbors
  BYTE dir,nCount

  FillScreen()

  Color=2
  InitStack()
  Push(x,y)
  WHILE IsEmpty()=0
  DO
    Pop(@x,@y)
    GetNeighbors(x,y,neighbors,@nCount)
    IF nCount>0 THEN
      Push(x,y)
      Plot(x,y)
      dir=neighbors(Rand(nCount))
      IF dir=TOP THEN
        y==-2
      ELSEIF dir=RIGHT THEN
        x==+2
      ELSEIF dir=BOTTOM THEN
        y==+2
      ELSE
        x==-2
      FI
      DrawTo(x,y)
      Push(x,y)
    FI
  OD
RETURN

PROC Main()
  BYTE CH=$02FC,COLOR0=$02C4,COLOR1=$02C5
  BYTE x,y

  Graphics(7+16)
  COLOR0=$0A
  COLOR1=$04

  x=Rand((WIDTH RSH 1)-1) LSH 1+1
  y=Rand((HEIGHT RSH 1)-1) LSH 1+1
  Maze(x,y)

  DO UNTIL CH#$FF OD
  CH=$FF
RETURN


generic
   Height : Positive;
   Width : Positive;
package Mazes is

   type Maze_Grid is private;

   procedure Initialize (Maze : in out Maze_Grid);

   procedure Put (Item : Maze_Grid);

private

   type Directions is (North, South, West, East);

   type Cell_Walls is array (Directions) of Boolean;
   type Cells is record
      Walls   : Cell_Walls := (others => True);
      Visited : Boolean    := False;
   end record;

   subtype Height_Type is Positive range 1 .. Height;
   subtype Width_Type is Positive range 1 .. Width;

   type Maze_Grid is array (Height_Type, Width_Type) of Cells;

end Mazes;


grid_maze(data b, integer N)
{
    data d;

    N.times(bb_cast, d, "+---");
    bb_cast(d, "+\n");

    N.times(bb_cast, d, "| * ");
    bb_cast(d, "|\n");

    N.times(bb_copy, b, d);

    b_size(d, N * 4 + 2);

    bb_copy(b, d);
}

void
walk_cell(data b, integer N, line_size, x, y, list x_offsets, y_offsets)
{
    integer i, p, q, r;

    b_replace(b, y + x, ' ');

    r = drand(3);

    i = 0;
    while (i < 4) {
        p = x + x_offsets[q = (r + i) & 3];
        q = y + y_offsets[q];

        if (-1 < p && p < line_size
            && -1 < q && q < line_size * (N * 2 + 1)) {
            if (b[q + p] == '*') {
                walk_cell(b, N, line_size, p, q, x_offsets, y_offsets);
                b[(q + y) / 2 + (p + x) / 2] = ' ';
                if (p == x) {
                    b[(q + y) / 2 + p - 1] = ' ';
                    b[(q + y) / 2 + p + 1] = ' ';
                }
            }
        }

        i += 1;
    }
}

walk_maze(data b, integer N)
{
    integer line_size, x, y;
    list x_offsets, y_offsets;

    line_size = N * 4 + 1 + 1;

    l_bill(x_offsets, 0, 4, 0, -4, 0);
    l_bill(y_offsets, 0, 0, line_size * 2, 0, line_size * -2);

    x = drand(N - 1) * 4 + 2;
    y = line_size * (drand(N - 1) * 2 + 1);

    walk_cell(b, N, line_size, x, y, x_offsets, y_offsets);
}

main(void)
{
    data b;

    grid_maze(b, 10);
    walk_maze(b, 10);

    o_(b);

    0;
}


This example shows how to use GNU APL scripting.

#!/usr/local/bin/apl --script --
 ⍝⍝⍝⍝⍝⍝⍝⍝⍝⍝⍝⍝⍝⍝⍝⍝⍝⍝⍝⍝⍝⍝⍝⍝⍝⍝⍝⍝⍝⍝⍝⍝⍝⍝⍝⍝⍝⍝⍝⍝⍝⍝⍝⍝⍝⍝⍝⍝⍝⍝⍝⍝⍝⍝⍝⍝⍝⍝⍝⍝⍝⍝⍝⍝⍝⍝⍝⍝
⍝                                                                    ⍝
⍝ mazeGen.apl                          2022-01-07  19:47:35 (GMT-8)  ⍝
⍝                                                                    ⍝
 ⍝⍝⍝⍝⍝⍝⍝⍝⍝⍝⍝⍝⍝⍝⍝⍝⍝⍝⍝⍝⍝⍝⍝⍝⍝⍝⍝⍝⍝⍝⍝⍝⍝⍝⍝⍝⍝⍝⍝⍝⍝⍝⍝⍝⍝⍝⍝⍝⍝⍝⍝⍝⍝⍝⍝⍝⍝⍝⍝⍝⍝⍝⍝⍝⍝⍝⍝⍝

∇initPRNG
  ⍝⍝ Seed the internal PRNG used by APL ? operator
  ⎕RL ← +/ ⎕TS   ⍝⍝ Not great... but good enough
∇

∇offs ← cellTo dir
  ⍝⍝ Return the offset (row col) to cell which lies in compass (dir)
  offs ← ∊((¯1 0)(0 1)(1 0)(0 ¯1))[('nesw'⍳dir)]
∇

∇doMaze rc
  ⍝⍝ Main function
  0 0 mazeGen rc      ⍝⍝ Do the maze gen
  m                   ⍝⍝ output result
∇

∇b ← m isVisited coord;mr;mc
  →( ∨/ (coord[1] < 1) (coord[2] < 1) )/yes
  →( ∨/ (coord > ⌊(⍴m)÷2) )/yes
  b ← ' ' ∊ m[2×coord[1];2×coord[2]]
  →0
yes:
  b←1
∇

∇c mazeGen sz ;dirs;c;dir;cell;next
  →(c≠(0 0))/gen
init:
  c ← ?sz[1],?sz[2]
  m ← mazeInit sz
gen:
  cell ← c
  dirs ← 'nesw'[4?4]
  m[2×c[1];2×c[2]] ← ' '  ⍝ mark cell as visited
dir1:
  dir ← dirs[1]
  next ← cell + cellTo dir
  →(m isVisited next)/dir2
  m ← m openWall cell dir
  next mazeGen sz
dir2:
  dir ← dirs[2]
  next ← cell + cellTo dir
  →(m isVisited next)/dir3
  m ← m openWall cell dir
  next mazeGen sz
dir3:
  dir ← dirs[3]
  next ← cell + cellTo dir
  →(m isVisited next)/dir4
  m ← m openWall cell dir
  next mazeGen sz
dir4:
  dir ← dirs[4]
  next ← cell + cellTo dir
  →(m isVisited next)/done
  m ← m openWall cell dir
  next mazeGen sz
done:
∇

∇m ← mazeInit sz;rows;cols;r
  ⍝⍝ Init an ASCII grid which
  ⍝⍝ has all closed and unvisited cells:
  ⍝⍝
  ⍝⍝  +-+
  ⍝⍝  |.|
  ⍝⍝  +-+
  ⍝⍝
  ⍝⍝ @param sz - tuple (rows cols)
  ⍝⍝ @return m - ASCII representation of (rows × cols) closed maze cells
  ⍝⍝⍝⍝

  initPRNG
  (rows cols) ← sz
  r ← ∊ (cols ⍴ ⊂"+-" ),"+"
  r ← r,∊ (cols ⍴ ⊂"|." ),"|"
  r ← (rows,(⍴r))⍴r
  r ← ((2×rows),(1+2×cols))⍴r
  r ← r⍪ (∊ (cols ⍴ ⊂"+-" ),"+")
  m ← r
∇

∇r ← m openWall cellAndDir ;ri;ci;rw;cw;row;col;dir
  (row col dir) ← ∊cellAndDir
  ri ← 2×row
  ci ← 2×col
  (rw cw) ← (ri ci) + cellTo dir
  m[rw;cw] ← ' '   ⍝ open wall in (dir)
  r ← m
∇

⎕IO←1

doMaze 9 9
)OFF


; Initially build the board
Width	:= 11
Height	:= 8
Loop % height*2+1
{
	Outer := A_Index
	Loop % Width
		maze .= Outer & 1 ? "+-" : "|0"
	maze .= (Outer & 1 ? "+" : "|") "`n"
}
StringTrimRight, maze, maze, 1 ; removes trailing newline
Clipboard := Walk(maze)

Walk(S, x=0, y=0){
	If !x{	; --Start at a random cell...
		StringReplace, junk, S, `n,,UseErrorLevel ; Calculate rows
		Random, y, 1, ErrorLevel//2
		Random, x, 1, InStr(S, "`n")//2-1         ; Calculate height
	}

	; --Obtain a list of its neighbors...
	neighbors := x "," y+1 "`n" x "," y-1 "`n" x+1 "," y "`n" x-1 "," y
	; --Randomize the list...
	Sort neighbors, random

	; --Then for each neighbor...
	Loop Parse, neighbors, `n
	{
		pC := InStr(A_LoopField, ","), x2 := SubStr(A_LoopField, 1, pC-1), y2 := SubStr(A_LoopField, pC+1)
		; If it has not been visited...
		If GetChar(S, 2*x2, 2*y2) = "0"{
			; Mark it as visited...
			S := ChangeChar(s, 2*x2, 2*y2, " ")
			; Remove the wall between this cell and the neighbor...
			S := ChangeChar(S, x+x2, y+y2, " ")
			; Then recurse with the neighbor as the current cell
			S := Walk(S, x2, y2)
		}
	}
	return S
}

; Change a character in a string using x and y coordinates
ChangeChar(s, x, y, c){
	Loop Parse, s, `n
	{
		If (A_Index = Y)
			Loop Parse, A_LoopField
				If (A_Index = x)
					out .= c
				Else	out .= A_LoopField
		Else out .= A_LoopField
		out .= "`n"
	}
	StringTrimRight, out, out, 1
	return out
}

; retrieve a character in a string using x and y coordinates
GetChar(s, x, y, n=1){
	x*=n, y*=n
	Loop Parse, s, `n
		If (A_Index = Y)
			return SubStr(A_LoopField, x, 1)
}


#!/usr/bin/awk -f

# Remember: AWK is 1-based, for better or worse.

BEGIN {
    # The maze dimensions.
    width = 20;  # Global
    height = 20; # Global
    resetMaze();

    # Some constants.
    top = 1;
    bottom = 2;
    left = 3;
    right = 4;

    # Randomize the PRNG.
    randomize();

    # Visit all the cells starting at a random point.
    visitCell(getRandX(), getRandY());

    # Show the result.
    printMaze();
}

# Wander through the maze removing walls as we go.
function visitCell(x, y,    dirList, dir, nx, ny, ndir, pi) {
    setVisited(x, y);   # This cell has been visited.

    # Visit neighbors in a random order.
    dirList = getRandDirList();
    for (dir = 1; dir <= 4; dir++) {
        # Get coordinates of a random neighbor (next in random direction list).
        ndir = substr(dirList, dir, 1);
        nx = getNextX(x, ndir);
        ny = getNextY(y, ndir);

        # Visit an unvisited neighbor, removing the separating walls.
        if (wasVisited(nx, ny) == 0) {
            rmWall(x, y, ndir);
            rmWall(nx, ny, getOppositeDir(ndir));
            visitCell(nx, ny)
        }
    }
}

# Display the text-mode maze.
function printMaze(    x, y, r, w) {
    for (y = 1; y <= height; y++) {
        for (pass = 1; pass <= 2; pass++) { # Go over each row twice: top, middle
            for (x = 1; x <= width; x++) {
                if (pass == 1) { # top
                    printf("+");
                    printf(hasWall(x, y, top) == 1 ? "---" : "   ");
                    if (x == width) printf("+");
                }
                else if (pass == 2) { # left, right
                    printf(hasWall(x, y, left) == 1 ? "|" : " ");
                    printf("   ");
                    if (x == width) printf(hasWall(x, y, right) == 1 ? "|" : " ");
                }
            }
            print;
        }
    }
    for (x = 1; x <= width; x++) printf("+---"); # bottom row
    print("+"); # bottom right corner
}

# Given a direction, get its opposite.
function getOppositeDir(d) {
    if (d == top) return bottom;
    if (d == bottom) return top;
    if (d == left) return right;
    if (d == right) return left;
}

# Build a list (string) of the four directions in random order.
function getRandDirList(    dirList, randDir, nx, ny, idx) {
    dirList = "";
    while (length(dirList) < 4) {
        randDir = getRandDir();
        if (!index(dirList, randDir)) {
            dirList = dirList randDir;
        }
    }
    return dirList;
}

# Get x coordinate of the neighbor in a given a direction.
function getNextX(x, dir) {
    if (dir == left) x = x - 1;
    if (dir == right) x = x + 1;
    if (!isGoodXY(x, 1)) return -1; # Off the edge.
    return x;
}

# Get y coordinate of the neighbor in a given a direction.
function getNextY(y, dir) {
    if (dir == top) y = y - 1;
    if (dir == bottom) y = y + 1;
    if (!isGoodXY(1, y)) return -1; # Off the edge.
    return y;
}

# Mark a cell as visited.
function setVisited(x, y,    cell) {
    cell = getCell(x, y);
    if (cell == -1) return;
    cell = substr(cell, 1, 4) "1"; # walls plus visited
    setCell(x, y, cell);
}

# Get the visited state of a cell.
function wasVisited(x, y,    cell) {
    cell = getCell(x, y);
    if (cell == -1) return 1; # Off edges already visited.
    return substr(getCell(x,y), 5, 1);
}

# Remove a cell's wall in a given direction.
function rmWall(x, y, d,    i, oldCell, newCell) {
    oldCell = getCell(x, y);
    if (oldCell == -1) return;
    newCell = "";
    for (i = 1; i <= 4; i++) { # Ugly as concat of two substrings and a constant?.
        newCell = newCell (i == d ? "0" : substr(oldCell, i, 1));
    }
    newCell = newCell wasVisited(x, y);
    setCell(x, y, newCell);
}

# Determine if a cell has a wall in a given direction.
function hasWall(x, y, d,    cell) {
    cell = getCell(x, y);
    if (cell == -1) return 1; # Cells off edge always have all walls.
    return substr(getCell(x, y), d, 1);
}

# Plunk a cell into the maze.
function setCell(x, y, cell,    idx) {
    if (!isGoodXY(x, y)) return;
    maze[x, y] = cell
}

# Get a cell from the maze.
function getCell(x, y,    idx) {
    if (!isGoodXY(x, y)) return -1; # Bad cell marker.
    return maze[x, y];
}

# Are the given coordinates in the maze?
function isGoodXY(x, y) {
    if (x < 1 || x > width) return 0;
    if (y < 1 || y > height) return 0;
    return 1;
}

# Build the empty maze.
function resetMaze(    x, y) {
    delete maze;
    for (y = 1; y <= height; y++) {
        for (x = 1; x <= width; x++) {
            maze[x, y] = "11110"; # walls (up, down, left, right) and visited state.
        }
    }
}

# Random things properly scaled.

function getRandX() {
    return 1 + int(rand() * width);
}

function getRandY() {
    return 1 +int(rand() * height);
}

function getRandDir() {
    return 1 + int(rand() * 4);
}

function randomize() {
    "echo $RANDOM" | getline t;
    srand(t);
}


OPTION BASE 0
RANDOMIZE TIMER

REM must be even
width% = 40
height% = 20

REM make array and fill
DIM maze$(width%, height%)
FOR x% = 0 TO width%
    FOR y% = 0 TO height%
        maze$(x%, y%) = "#"
    NEXT y%
NEXT x%

REM initial start location
currentx% = INT(RND * (width% - 1))
currenty% = INT(RND * (height% - 1))
REM value must be odd
IF currentx% MOD 2 = 0 THEN currentx% = currentx% + 1
IF currenty% MOD 2 = 0 THEN currenty% = currenty% + 1
maze$(currentx%, currenty%) = " "

REM generate maze
done% = 0
DO WHILE done% = 0
    FOR i% = 0 TO 99
        oldx% = currentx%
        oldy% = currenty%

        REM move in random direction
        SELECT CASE INT(RND * 4)
            CASE 0
                IF currentx% + 2 < width% THEN currentx% = currentx% + 2
            CASE 1
                IF currenty% + 2 < height% THEN currenty% = currenty% + 2
            CASE 2
                IF currentx% - 2 > 0 THEN currentx% = currentx% - 2
            CASE 3
                IF currenty% - 2 > 0 THEN currenty% = currenty% - 2
        END SELECT

        REM if cell is unvisited then connect it
        IF maze$(currentx%, currenty%) = "#" THEN
            maze$(currentx%, currenty%) = " "
            maze$(INT((currentx% + oldx%) / 2), ((currenty% + oldy%) / 2)) = " "
        END IF
    NEXT i%

    REM check if all cells are visited
    done% = 1
    FOR x% = 1 TO width% - 1 STEP 2
        FOR y% = 1 TO height% - 1 STEP 2
            IF maze$(x%, y%) = "#" THEN done% = 0
        NEXT y%
    NEXT x%
LOOP

REM draw maze
FOR y% = 0 TO height%
    FOR x% = 0 TO width%
        PRINT maze$(x%, y%);
    NEXT x%
    PRINT
NEXT y%

REM wait
DO: LOOP WHILE INKEY$ = ""


:amaze Rows Cols [wall char]
:: A stack-less, iterative, depth-first maze generator in native WinNT batch.
:: Rows and Cols must each be >1 and Rows*Cols cannot exceed 2096.
:: Default wall character is #, [wall char] is used if provided.

@ECHO OFF
SETLOCAL EnableDelayedExpansion

:: check for valid input, else GOTO :help
IF /I "%~2" EQU "" GOTO :amaze_help
FOR /F "tokens=* delims=0123456789" %%A IN ("%~1%~2") DO IF "%%~A" NEQ "" GOTO :amaze_help
SET /A "rows=%~1, cols=%~2, mTmp=rows*cols"
IF !rows! LSS 2    GOTO :amaze_help
IF !cols! LSS 2    GOTO :amaze_help
IF !mTmp! GTR 2096 GOTO :amaze_help

:: set map characters and use 1st character of %3 for wall, if defined
SET "wall=#"
SET "hall= "
SET "crumb=."
IF "%~3" NEQ "" SET "wall=%~3"
SET "wall=!wall:~0,1!"

:: assign width, height, cursor position, loop count, and offsets for NSEW
SET /A "cnt=0, wide=cols*2-1, high=rows*2-1, size=wide*high, N=wide*-2, S=wide*2, E=2, W=-2"

:: different random entrance points
:: ...on top
:: SET /A "start=(!RANDOM! %% cols)*2"
:: ...on bottom
:: SET /A "start=size-(!RANDOM! %% cols)*2-1"
:: ...on top or bottom
:: SET /A ch=cols*2, ch=!RANDOM! %% ch
:: IF !ch! GEQ !cols! ( SET /A "start=size-(ch-cols)*2-1"
:: ) ELSE SET /A start=ch*2
:: random entrance inside maze
SET /A "start=(!RANDOM! %% cols*2)+(!RANDOM! %% rows*2)*wide"
SET /A "curPos=start, cTmp=curPos+1, loops=cols*rows*2+1"

:: fill the maze with 8186 wall characters, clip to size, and open 1st cell
SET "mz=!wall!"
FOR /L %%A IN (1,1,6) DO SET mz=!mz!!mz!!mz!!mz!
SET bdr=!mz:~-%wide%!
SET mz=!mz:~3!!mz:~3!
SET mz=!mz:~-%size%!
SET mz=!mz:~0,%curPos%!!hall!!mz:~%cTmp%!

:: iterate #cells*2+1 steps of random depth-first search
FOR /L %%@ IN (1,1,%loops%) DO (
	SET "rand=" & SET "crmPos="
	REM set values for NSEW cell and wall positions
	SET /A "rCnt=rTmp=0, cTmp=curPos+1, np=curPos+N, sp=curPos+S, ep=curPos+E, wp=curPos+W, wChk=curPos/wide*wide, eChk=wChk+wide, nw=curPos-wide, sw=curPos+wide, ew=curPos+1, ww=curPos-1"
	REM examine adjacent cells, build direction list, and find last crumb position
	FOR /F "tokens=1-8" %%A IN ("!np! !sp! !ep! !wp! !nw! !sw! !ew! !ww!") DO (
		IF !np! GEQ 0 IF "!mz:~%%A,1!" EQU "!wall!" ( SET /A rCnt+=1 & SET "rand=n !rand!"
		) ELSE IF "!mz:~%%E,1!" EQU "!crumb!" SET /A crmPos=np, cw=nw
		IF !sp! LEQ !size! IF "!mz:~%%B,1!" EQU "!wall!" ( SET /A rCnt+=1 & SET "rand=s !rand!"
		) ELSE IF "!mz:~%%F,1!" EQU "!crumb!" SET /A crmPos=sp, cw=sw
		IF !ep! LEQ !eChk! IF "!mz:~%%C,1!" EQU "!wall!" ( SET /A rCnt+=1 & SET "rand=e !rand!"
		) ELSE IF "!mz:~%%G,1!" EQU "!crumb!" SET /A crmPos=ep, cw=ew
		IF !wp! GEQ !wChk! IF "!mz:~%%D,1!" EQU "!wall!" ( SET /A rCnt+=1 & SET "rand=w !rand!"
		) ELSE IF "!mz:~%%H,1!" EQU "!crumb!" SET /A crmPos=wp, cw=ww
	)
	IF DEFINED rand ( REM adjacent unvisited cell is available
		SET /A rCnt=!RANDOM! %% rCnt
		FOR %%A IN (!rand!) DO ( REM pick random cell + wall
			IF !rTmp! EQU !rCnt! SET /A "curPos=!%%Ap!, cTmp=curPos+1, mw=!%%Aw!, mTmp=mw+1"
			SET /A rTmp+=1
		)
		REM write the 2 new characters into the maze
		FOR /F "tokens=1-4" %%A IN ("!mw! !mTmp! !curPos! !cTmp!") DO (
			SET "mz=!mz:~0,%%A!!crumb!!mz:~%%B!"
			SET "mz=!mz:~0,%%C!!hall!!mz:~%%D!"
		)
	) ELSE IF DEFINED crmPos ( REM follow the crumbs backward
		SET /A mTmp=cw+1
		REM erase the crumb character and set new cursor position
		FOR /F "tokens=1-2" %%A IN ("!cw! !mTmp!") DO SET "mz=!mz:~0,%%A!!hall!!mz:~%%B!"
		SET "curPos=!crmPos!"
	)
)
SET /A open=cols/2*2, mTmp=open+1
ECHO !wall!!bdr:~0,%open%!!hall!!bdr:~%mTmp%!!wall!
FOR /L %%A IN (0,!wide!,!size!) DO IF %%A LSS !size! ECHO !wall!!mz:~%%A,%wide%!!wall!
ECHO !wall!!bdr:~0,%open%!!hall!!bdr:~%mTmp%!!wall!
ENDLOCAL
EXIT /B 0

:amaze_help
ECHO Usage:   %~0 Rows Cols [wall char]
ECHO          Rows^>1, Cols^>1, and Rows*Cols^<=2096
ECHO Example: %~0 11 39 @
ENDLOCAL
EXIT /B 0


      MazeWidth% = 11
      MazeHeight% = 9
      MazeCell% = 50

      VDU 23,22,MazeWidth%*MazeCell%/2+3;MazeHeight%*MazeCell%/2+3;8,16,16,128
      VDU 23,23,3;0;0;0; : REM Line thickness
      PROCgeneratemaze(Maze&(), MazeWidth%, MazeHeight%, MazeCell%)
      END

      DEF PROCgeneratemaze(RETURN m&(), w%, h%, s%)
      LOCAL x%, y%
      DIM m&(w%, h%)
      FOR y% = 0 TO h%
        LINE 0,y%*s%,w%*s%,y%*s%
      NEXT
      FOR x% = 0 TO w%
        LINE x%*s%,0,x%*s%,h%*s%
      NEXT
      GCOL 15
      PROCcell(m&(), RND(w%)-1, y% = RND(h%)-1, w%, h%, s%)
      ENDPROC

      DEF PROCcell(m&(), x%, y%, w%, h%, s%)
      LOCAL i%, p%, q%, r%
      m&(x%,y%) OR= &40 : REM Mark visited
      r% = RND(4)
      FOR i% = r% TO r%+3
        CASE i% MOD 4 OF
          WHEN 0: p% = x%-1 : q% = y%
          WHEN 1: p% = x%+1 : q% = y%
          WHEN 2: p% = x% : q% = y%-1
          WHEN 3: p% = x% : q% = y%+1
        ENDCASE
        IF p% >= 0 IF p% < w% IF q% >= 0 IF q% < h% IF m&(p%,q%) < &40 THEN
          IF p% > x% m&(p%,q%) OR= 1 : LINE p%*s%,y%*s%+4,p%*s%,(y%+1)*s%-4
          IF q% > y% m&(p%,q%) OR= 2 : LINE x%*s%+4,q%*s%,(x%+1)*s%-4,q%*s%
          IF x% > p% m&(x%,y%) OR= 1 : LINE x%*s%,y%*s%+4,x%*s%,(y%+1)*s%-4
          IF y% > q% m&(x%,y%) OR= 2 : LINE x%*s%+4,y%*s%,(x%+1)*s%-4,y%*s%
          PROCcell(m&(), p%, q%, w%, h%, s%)
        ENDIF
      NEXT
      ENDPROC


#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <locale.h>

#define DOUBLE_SPACE 1

#if DOUBLE_SPACE
#	define SPC "　"
#else
#	define SPC " "
#endif

wchar_t glyph[] = L""SPC"│││─┘┐┤─└┌├─┴┬┼"SPC"┆┆┆┄╯╮ ┄╰╭ ┄";

typedef unsigned char byte;
enum { N = 1, S = 2, W = 4, E = 8, V = 16 };

byte **cell;
int w, h, avail;
#define each(i, x, y) for (i = x; i <= y; i++)

int irand(int n)
{
	int r, rmax = n * (RAND_MAX / n);
	while ((r = rand()) >= rmax);
	return r / (RAND_MAX/n);
}

void show()
{
	int i, j, c;
	each(i, 0, 2 * h) {
		each(j, 0, 2 * w) {
			c = cell[i][j];
			if (c > V) printf("\033[31m");
			printf("%lc", glyph[c]);
			if (c > V) printf("\033[m");
		}
		putchar('\n');
	}
}

inline int max(int a, int b) { return a >= b ? a : b; }
inline int min(int a, int b) { return b >= a ? a : b; }

static int dirs[4][2] = {{-2, 0}, {0, 2}, {2, 0}, {0, -2}};
void walk(int x, int y)
{
	int i, t, x1, y1, d[4] = { 0, 1, 2, 3 };

	cell[y][x] |= V;
	avail--;

	for (x1 = 3; x1; x1--)
		if (x1 != (y1 = irand(x1 + 1)))
			i = d[x1], d[x1] = d[y1], d[y1] = i;

	for (i = 0; avail && i < 4; i++) {
		x1 = x + dirs[ d[i] ][0], y1 = y + dirs[ d[i] ][1];

		if (cell[y1][x1] & V) continue;

		/* break walls */
		if (x1 == x) {
			t = (y + y1) / 2;
			cell[t][x+1] &= ~W, cell[t][x] &= ~(E|W), cell[t][x-1] &= ~E;
		} else if (y1 == y) {
			t = (x + x1)/2;
			cell[y-1][t] &= ~S, cell[y][t] &= ~(N|S), cell[y+1][t] &= ~N;
		}
		walk(x1, y1);
	}
}

int solve(int x, int y, int tox, int toy)
{
	int i, t, x1, y1;

	cell[y][x] |= V;
	if (x == tox && y == toy) return 1;

	each(i, 0, 3) {
		x1 = x + dirs[i][0], y1 = y + dirs[i][1];
		if (cell[y1][x1]) continue;

		/* mark path */
		if (x1 == x) {
			t = (y + y1)/2;
			if (cell[t][x] || !solve(x1, y1, tox, toy)) continue;

			cell[t-1][x] |= S, cell[t][x] |= V|N|S, cell[t+1][x] |= N;
		} else if (y1 == y) {
			t = (x + x1)/2;
			if (cell[y][t] || !solve(x1, y1, tox, toy)) continue;

			cell[y][t-1] |= E, cell[y][t] |= V|E|W, cell[y][t+1] |= W;
		}
		return 1;
	}

	/* backtrack */
	cell[y][x] &= ~V;
	return 0;
}

void make_maze()
{
	int i, j;
	int h2 = 2 * h + 2, w2 = 2 * w + 2;
	byte **p;

	p = calloc(sizeof(byte*) * (h2 + 2) + w2 * h2 + 1, 1);

	p[1] = (byte*)(p + h2 + 2) + 1;
	each(i, 2, h2) p[i] = p[i-1] + w2;
	p[0] = p[h2];
	cell = &p[1];

	each(i, -1, 2 * h + 1) cell[i][-1] = cell[i][w2 - 1] = V;
	each(j, 0, 2 * w) cell[-1][j] = cell[h2 - 1][j] = V;
	each(i, 0, h) each(j, 0, 2 * w) cell[2*i][j] |= E|W;
	each(i, 0, 2 * h) each(j, 0, w) cell[i][2*j] |= N|S;
	each(j, 0, 2 * w) cell[0][j] &= ~N, cell[2*h][j] &= ~S;
	each(i, 0, 2 * h) cell[i][0] &= ~W, cell[i][2*w] &= ~E;

	avail = w * h;
	walk(irand(2) * 2 + 1, irand(h) * 2 + 1);

	/* reset visited marker (it's also used by path finder) */
	each(i, 0, 2 * h) each(j, 0, 2 * w) cell[i][j] &= ~V;
	solve(1, 1, 2 * w - 1, 2 * h - 1);

	show();
}

int main(int c, char **v)
{
	setlocale(LC_ALL, "");
	if (c < 2 || (w = atoi(v[1])) <= 0) w = 16;
	if (c < 3 || (h = atoi(v[2])) <= 0) h = 8;

	make_maze();

	return 0;
}


using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.Linq;
using System.Text;
using System.Drawing;

namespace MazeGeneration
{
    public static class Extensions
    {
        public static IEnumerable<T> Shuffle<T>(this IEnumerable<T> source, Random rng)
        {
            var e = source.ToArray();
            for (var i = e.Length - 1; i >= 0; i--)
            {
                var swapIndex = rng.Next(i + 1);
                yield return e[swapIndex];
                e[swapIndex] = e[i];
            }
        }

        public static CellState OppositeWall(this CellState orig)
        {
            return (CellState)(((int) orig >> 2) | ((int) orig << 2)) & CellState.Initial;
        }

        public static bool HasFlag(this CellState cs,CellState flag)
        {
            return ((int)cs & (int)flag) != 0;
        }
    }

    [Flags]
    public enum CellState
    {
        Top = 1,
        Right = 2,
        Bottom = 4,
        Left = 8,
        Visited = 128,
        Initial = Top | Right | Bottom | Left,
    }

    public struct RemoveWallAction
    {
        public Point Neighbour;
        public CellState Wall;
    }

    public class Maze
    {
        private readonly CellState[,] _cells;
        private readonly int _width;
        private readonly int _height;
        private readonly Random _rng;

        public Maze(int width, int height)
        {
            _width = width;
            _height = height;
            _cells = new CellState[width, height];
            for(var x=0; x<width; x++)
                for(var y=0; y<height; y++)
                    _cells[x, y] = CellState.Initial;
            _rng = new Random();
            VisitCell(_rng.Next(width), _rng.Next(height));
        }

        public CellState this[int x, int y]
        {
            get { return _cells[x,y]; }
            set { _cells[x,y] = value; }
        }

        public IEnumerable<RemoveWallAction> GetNeighbours(Point p)
        {
            if (p.X > 0) yield return new RemoveWallAction {Neighbour = new Point(p.X - 1, p.Y), Wall = CellState.Left};
            if (p.Y > 0) yield return new RemoveWallAction {Neighbour = new Point(p.X, p.Y - 1), Wall = CellState.Top};
            if (p.X < _width-1) yield return new RemoveWallAction {Neighbour = new Point(p.X + 1, p.Y), Wall = CellState.Right};
            if (p.Y < _height-1) yield return new RemoveWallAction {Neighbour = new Point(p.X, p.Y + 1), Wall = CellState.Bottom};
        }

        public void VisitCell(int x, int y)
        {
            this[x,y] |= CellState.Visited;
            foreach (var p in GetNeighbours(new Point(x, y)).Shuffle(_rng).Where(z => !(this[z.Neighbour.X, z.Neighbour.Y].HasFlag(CellState.Visited))))
            {
                this[x, y] -= p.Wall;
                this[p.Neighbour.X, p.Neighbour.Y] -= p.Wall.OppositeWall();
                VisitCell(p.Neighbour.X, p.Neighbour.Y);
            }
        }

        public void Display()
        {
            var firstLine = string.Empty;
            for (var y = 0; y < _height; y++)
            {
                var sbTop = new StringBuilder();
                var sbMid = new StringBuilder();
                for (var x = 0; x < _width; x++)
                {
                    sbTop.Append(this[x, y].HasFlag(CellState.Top) ? "+--" : "+  ");
                    sbMid.Append(this[x, y].HasFlag(CellState.Left) ? "|  " : "   ");
                }
                if (firstLine == string.Empty)
                    firstLine = sbTop.ToString();
                Debug.WriteLine(sbTop + "+");
                Debug.WriteLine(sbMid + "|");
                Debug.WriteLine(sbMid + "|");
            }
            Debug.WriteLine(firstLine);
        }
    }

    class Program
    {
        static void Main(string[] args)
        {
            var maze = new Maze(20, 20);
            maze.Display();
        }
    }
}