15-en, BKE en het magisch vierkant

Inhoud

Inleiding

In dit artikel gaan we kijken naar drie verschillende onderwerpen.

Allereerst kijken we naar het spelletje 15-en dat met 2 personen kan worden gespeeld of met 1 persoon tegen de computer.

Daarna kijken we naar het beroemde spelletje Boter-Kaas-en-Eieren (BKE) en gaan we op zoek naar een goede strategie.

Tot slot kijken we naar het magisch vierkant. Dit is een vierkant van 3×3 met een bijzondere eigenschap.

Zo op het oog nogal een ratjetoe van totaal verschillende onderwerpen.
Maar schijn bedriegt…

15-en

Het spel 15-en gaat als volgt, we gaan even ervan uit dat 2 personen tegen elkaar spelen:

Leg op tafel 9 kaartjes neer met daarop respectievelijk de getallen 1 tot en met 9.

Om de beurt mag een speler 1 van de kaartjes op tafel pakken.

Wanneer een speler met 3 van zijn kaartjes de som van 15 kan maken heeft deze gewonnen.
Het hoeven niet per se de laatste 3 gekozen kaartjes te zijn.

Voorbeeld:

Speler 1 kiest kaartje met nummer 7 erop (in ’t kort: S1G7),
S2G5,
S1G3,
S2G4,
S1G8

Speler 1 heeft nu 3 kaarten met de waardes: 7, 3 en 8. Hiermee kan hij /zij geen 15 maken.

S2G1

Speler 2 heeft nu 3 kaarten met de waardes: 5, 4 en 1 en hiermee kan hij/zij geen 15 maken.

S1G2

Speler 1 kan nog steeds geen 15 maken met 3 van zijn/haar 4 kaarten.

S2G9

Speler 2 heeft nu de kaarten: 4, 5, 1 en 9 en kan nu wel 15 maken. Namelijk met de kaarten 4, 1 en 9 want 4+1+9=15.

Speler 2 heeft dus gewonnen!

Met het onderstaande Python programma kun je dit spel tegen de computer spelen.
Doe dat maar eens een aantal keer en kijk of je van de computer kan winnen…

Python 15-en

# 15-en

import random
from itertools import combinations

stock=[]
human=[]
computer=[]
move=0
player=’computer’
stop=False
defensive=True

def question(question, default):
    a=input(question+’ [‘+default+’]: ‘)
    if a==” or a[0].upper()==default.upper():
        return True
    else:
    return False

def resetup():
    global stock, human, computer, move, player

    for i in range(1, 10):
        stock.append(i)
    human=[]
    computer=[]
    move=0
    if player==’computer’:
        player=’human’
    else:
        player=’computer’

def setup():
    global player,defensive

    print(’15-en’)
    print()
    q=’Do you want the computer play defensive(d) or at random(r)?’
    if question(q, ‘D’):
        defensive=True
    else:
        defensive=False
    if question(‘Do you want to start?’, ‘Yes’):
        player=’computer’
    else:
        player=’human’
    print()
    resetup()

def scoreboard():
    global stock, human, computer

    human=sorted(human)
    computer=sorted(computer)
    stock=sorted(stock)
    print(‘—–Scoreboard————————-‘)
    print(‘Stock:’, end=’ ‘)
    print(*stock, end=’ ‘)
    print(‘You:’, end=’ ‘)
    print(*human, end=’ ‘)
    print(‘Computer:’, end=’ ‘)
    print(*computer)
    print(‘—————————————-‘)

def check15():
    s=0
    c=[]
    if player==’human’:
        comb=combinations(human,3)
uitleg code

Met de functie combinations uit itertools worden de verschillende combinaties van de list human met een lengte van 3 bepaald. Het resultaat is een list.
        for i in comb:
            for j in range(3):
                s+=i[j]
                c.append(i[j])
        if s==15:
            break
        else:
            s=0
            c=[]
    else:
        comb=combinations(computer,3)
        for i in comb:
            for j in range(3):
                s+=i[j]
                c.append(i[j])
            if s==15:
                break
            else:
                s=0
                c=[]
    return (s==15), sorted(c)

def humanpick():
    global player, move, human

    wrong=True
    if move<9:
        scoreboard()
        while wrong:
            print(‘\nYour turn’)
            print(‘You can choose the following number(s): ‘, end=’ ‘)
            print(‘ ‘.join(str(x) for x in stock))
            try:
                g=int(input(‘Choose your number: ‘))
            except:
                print(‘You gave a false number: Try again…’)
                wrong=True
                continue
            if g==0:
                exit(0)
            if g in stock:
                stock.remove(g)
                human.append(g)
                wrong=False
            else:
                print(‘You choose a wrong number: Try again…’)
                wrong=True
        print()
        move+=1
        if move>4:
            w,c = check15()
            if w:
                print(‘You have won!!!’)
                print(‘Winning combination: ‘, end=’ ‘)
                print(*c)
                return 1
            else:
                player=’computer’
                return 0
        else:
            player=’computer’
            return 0

def computerpick():
    global player, move

    if move<9:
        scoreboard()
        print(‘\nMy turn:’)
        print(‘I can choose one of the following numbers: ‘, end=’ ‘)
        print(‘ ‘.join(str(x) for x in stock))

        wrong=True

        #defensive
        if defensive:
            if move>=3:
                temp=human.copy()
                s=0
                for i in range(1,10):
                    if not (i in temp) and (i in stock):
                        temp.append(i)
                        comb=combinations(temp,3)
                        for x in comb:
                            for y in range(3):
                                s+=x[y]
                        if s==15:
                            temp.remove(i)
                            wrong=False
                            g=i
                            break
                        else:
                            s=0
                            temp.remove(i)

        while wrong:
            g=random.randrange(1,10)
            wrong=not(g in stock)
        print(f’I choose {g}’)
        print()
        stock.remove(g)
        computer.append(g)
        move+=1
        if move>4:
            w,c = check15()
            if w:
                print(‘I have won!!!’)
                print(‘Winning combination: ‘, end=’ ‘)
                print(‘ ‘.join(str(x) for x in c))
                return 1
            else:
                player=’human’
                return 0
        else:
            player=’human’
            return 0

def play():
    global stop

    while not stop:
        if move>=9:
            print(‘It is a draw.’)
            stop=not(question(‘Play again? ‘, ‘Y’))
            if not stop:
                resetup()
            else:
                exit(0)
        elif player==’human’:
            if humanpick():
                stop=not(question(‘Play again? ‘, ‘Y’))
                if not stop:
                    resetup()
                else:
                    exit(0)
        elif player==’computer’:
            if computerpick():
                stop=not(question(‘Play again? ‘, ‘Y’))
                if not stop:
                    resetup()
                else:
                    exit(0)

setup()
play()

kopieer 15-en.py naar kladblok

Permutaties en combinaties

In de Python code wordt gebruik gemaakt van een functie die combinaties berekent.

Maar wat is ook al weer het verschil tussen combinaties en permutaties?

Voor beiden geldt dat je kijkt op hoeveel en op welke manier je k elementen uit een verzameling van n elementen haalt.

Voor de combinatie geldt dat de volgorde er niet toe doet.
Voor de permutatie doet de volgorde er wel toe.

De formule voor de combinatie luidt:

C(n,k)=nCr=\frac{n!}{k!(n-k)!}

De formule voor de permutatie luidt:

P(n,k)=nPr=\frac{n!}{(n-k)!}

Laten we eens kijken op hoeveel manieren je 3 elementen uit een verzameling van 4 elementen kunt halen:

Het aantal combinaties is:

C(4,3)=\frac{4!}{3!(4-3)!}=\frac{4!}{3!.1!}=\frac{24}{6}=4

Het aantal permutaties is:

P(4,3)=\frac{4!}{(4-3)!}=\frac{4!}{1!}=\frac{24}{1}=24

 

De vraag is nu: Is er een winnende strategie?

Boter-Kaas-en-Eieren

Dit spel is overbekend maar voor diegenen die het toch niet kennen even een korte uitleg:

Boter-Kaas-en-Eieren wordt gespeeld op een 3×3 bord tussen 2 spelers. De speler die begint speelt met een X en de andere speler speelt met een O. Per beurt mag een speler zijn/haar symbool op een leeg vak plaatsen. De eerste die een horizontale, verticale of diagonale rij met zij/haar symbool heeft gemaakt heeft gewonnen.

Een spel kan er als volgt uitzien:

Met het onderstaande Python programma kun je dit spel tegen de computer spelen.
Doe dat maar eens een aantal keer en kijk of je van de computer kan winnen…

Python BKE

# Tic-Tac-Toe: human vs computer
# Computer can play defensive

import random

board=[[‘.’,’.’,’.’],[‘.’,’.’,’.’],[‘.’,’.’,’.’]]
player=’X’
computer=’O’
turn=’human’
move=0
stop=False
#nt is used in printboard for normal-text(nt=True) or extended asccii-text(nt=False)
nt=False
defensive=True

def question(question, default):
    a=input(question+’ [‘+default+’]: ‘)
    if a==” or a[0].upper()==default.upper():
        return True
    else:
        return False

def printboard(normaltext=False):
    if normaltext:
        print(‘——-‘)
    else:
        print(‘╔═╦═╦═╗’)
    for x in range(3):
        if normaltext:
            print(f’|{board[x][0] if board[x][0] != “.” else 3*x+1}|’
f'{board[x][1] if board[x][1] != “.” else 3*x+2}|’
f'{board[x][2] if board[x][2] != “.” else 3*x+3}|\n——-‘)
        else:
            print(f’║{board[x][0] if board[x][0] != “.” else 3*x+1}║’
f'{board[x][1] if board[x][1] != “.” else 3*x+2}║’
f'{board[x][2] if board[x][2] != “.” else 3*x+3}║’)
            if x<2:
                print(‘╠═╬═╬═╣’)
    if normaltext:
        print()
    else:
        print(‘╚═╩═╩═╝’)

def setup():
    global player, computer, turn, nt, defensive

    q=’Do you want plain-text(p) or Ascii-text(a) for the grid?’
    if question(q, ‘A’):
        nt=False
    else:
        nt=True

    q=’Do you watn the computer play defensive(d) or at random(r)?’
    if question(q, ‘D’):
        defensive=True
    else:
        defensive=False

    q=’Do you want to play with “X”?’
    if question(q,’Yes’):
        player=’X’
        computer=’O’
        turn=’human’
    else:
        player=’O’
        computer=’X’
        turn=’computer’

def playerturn():
    global move, stop

    wrong=True
    while wrong:
        printboard(normaltext=nt)
        try:
            v=int(input(‘Wich field (number) do you want to use?’))
        except:
            print(‘false input: Give integer number’)
            wrong=True
            continue
        if v==0:
            stop=True
            exit(0)
        else:
            f=(v-1)//3
            g=(v)%3-1
            if g==-1:
                g=2
            wrong=(board[f][g] != ‘.’)
            if wrong:
                print(f’false input: Board({f+1}, {g+1}) (number {v}) is not free’)
    if not stop:
        board[f][g]=player
        move += 1

def givesum(cs):
    s=0
    #1st row horizontal
    if cs==1:
        for y in range(3):
            if board[0][y]==player:
                s+=2**y
    #2nd row horizontal
    if cs==2:
        for y in range(3):
            if board[1][y]==player:
                s+=2**(y+3)
    #3rd row horizontal
    if cs==3:
        for y in range(3):
            if board[2][y]==player:
                s+=2**(y+6)
    #1st column vertical
    if cs==4:
        for x in range(3):
            if board[x][0]==player:
                s+=2**(x*3)
    #2nd column vertical
    if cs==5:
        for x in range(3):
            if board[x][1]==player:
                s+=2**(x*3+1)
    #3rd column vertical
    if cs==6:
        for x in range(3):
            if board[x][2]==player:
                s+=2**(x*3+2)
    #Diagonal from upper left to lower right
    if cs==7:
        for x in range(3):
            if board[x][x]==player:
                s+=2**(4*x)
    #Diagonal from upper right to lower left
    if cs==8:
        for x in range(3):
            if board[2-x][x]==player:
                s+=4**(x+1)
    return s

def computerturn():
    global move

    #Defense
    f=-1
    if defensive:
uitleg code

Om niet eindeloos veel geneste if-elif-combinaties te krijgen is hier wat wiskunde toegepast. Ieder veld van het vierkant heeft een uniek binair nummer gekregen. Hiermee zal de som van iedere combinatie van cellen een unieke waarde opleveren. De binaire getallen zijn uiteraard omgezet in decimale getallen.
        for t in range(9):
            s=givesum(t)
            if s==3 or s==5 or s==6:
                p=7-s
                f=0
                g=p//2
                if board[int(f)][int(g)]==’.’:
                    break
                else:
                    f=-1
            if s==24 or s==40 or s==48:
                p=56-s
                f=1
                g=p//16
                if board[int(f)][int(g)]==’.’:
                    break
                else:
                    f=-1
            if s==192 or s==320 or s==384:
                p=448-s
                f=2
                g=p//128
                if board[int(f)][int(g)]==’.’:
                    break
                else:
                    f=-1
            if s==9 or s==65 or s==72:
                p=73-s
                g=0
                f=round((p**(1/3))*0.5)
                if board[int(f)][int(g)]==’.’:
                    break
                else:
                    f=-1
            if s==18 or s==130 or s==144:
                p=146-s
                g=1
                f=round(p**0.25)-1
                if board[int(f)][int(g)]==’.’:
                    break
                else:
                    f=-1
            if s==36 or s==260 or s==288:
                p=292-s
                g=2
                f=round((p**0.25)//2)
                if board[int(f)][int(g)]==’.’:
                    break
                else:
                    f=-1
            if s==17 or s==257 or s==272:
                p=273-s
                f=(p**0.25)//2
                g=f
                if board[int(f)][int(g)]==’.’:
                    break
                else:
                    f=-1
            if s==20 or s==68 or s==80:
                p=84-s
                g=(p**0.5)//4
                f=-g+2
                if board[int(f)][int(g)]==’.’:
                    break
                else:
                    f=-1

    if f==-1:
        wrong=True
        while wrong:
            v=random.randint(1,9)
            f=(v-1)//3
            g=(v)%3-1
            if g==-1:
                g=2
            wrong=(board[f][g] != ‘.’)
    board[int(f)][int(g)]=computer
    move += 1

def score():
    global turn

    pw=False
    cw=False
    for x in range(3):
        if board[x][0] == player and board[x][1] == player and board[x][2] == player:
            pw=True
    if not pw:
        for y in range(3):
            if board[0][y] == player and board[1][y] == player and board[2][y] == player:
                pw=True
    if not pw:
        if (board[0][0] == player and board[1][1] == player and board[2][2] == player) or \
        (board[0][2] == player and board[1][1] == player and board[2][0] == player):
            pw=True
    if pw:
        printboard(normaltext=nt)
        print(‘You have won!!!’)
        return 1

    for x in range(3):
        if board[x][0] == computer and board[x][1] == computer and board[x][2] == computer:
            cw=True
    if not pw:
        for y in range(3):
            if board[0][y] == computer and board[1][y] == computer and board[2][y] == computer:
                cw=True
    if not pw:
        if (board[0][0] == computer and board[1][1] == computer and board[2][2] == computer) or \
        (board[0][2] == computer and board[1][1] == computer and board[2][0] == computer):
            cw=True
    if cw:
        printboard(normaltext=nt)
        print(‘I have won!!!’)
        return 1

    if move == 9:
        printboard(normaltext=nt)
        print(“It’s a draw!”)
        return 1

    if turn == ‘human’:
        turn=’computer’
    else:
        turn=’human’
    return 0

def play():
    global turn, stop, player, computer, board, move

    while not stop:
        if turn==’human’:
            playerturn()
            p=score()
        else:
            computerturn()
            p=score()
        if p == 1:
            stop=not question(‘Play Again?’, ‘Yes’)
            move=0
            if not stop:
                board=[[‘.’,’.’,’.’],[‘.’,’.’,’.’],[‘.’,’.’,’.’]]
                if question(‘Do you want to play with “X”?’, ‘Yes’):
                    player=’X’
                    computer=’O’
                    turn=’human’
                else:
                    player=’O’
                    computer=’X’
                    turn=’computer’

setup()
print(f'{“You” if turn==”human” else “Computer”} play{“s” if turn==”Computer” else “”} with X.’)
play()

#defensive play:
#used grid
#         84
# 1 2 4 7
# 8 16 32 56
# 64 128 256 488
#
# 73 146 292 273
#
# numbers in grid are decimal representitives of binairy numbers (000000001 to 100000000),
# wich ensures unique sums of all different combinations of cells
#

kopieer tictactoe.py naar kladblok

En ook hier luidt de vraag: Is er een winnende strategie?

Het magisch vierkant

Een magisch vierkant is een vierkant gevuld met getallen waarbij (op z’n minst) de sommen van de rijen, kolommen en (hoofd-)diagonalen gelijk zijn.

Voorbeeld:

In bovenstaand 4×4-vierkant zijn de getallen 1 t/m 16 zodanig ingevuld dat de som van de rijen, kolommen en hoofddiagonalen allemaal gelijk zijn, namelijk 34.
Er zitten nog meer 2×2 vierkantjes in waarvan de som ook 34 bedraagt!

De moeder van alle magische vierkanten, HET magisch vierkant, is een vierkant van 3×3 waar de getallen 1 t/m 9 zodanig zijn ingevuld dat de som van de rijen, kolommen en diagonalen gelijk zijn, namelijk 15:

Om een 3×3 magisch vierkant te maken kun je gebruik maken van de volgende methode:

  • Plaats ergens een 1
  • Plaats het opeenvolgende getal rechts boven het vorige getal
  • Wanneer je op de bovenste rij en/of de meest rechtse kolom bent doe dan alsof het vierkant een bol is (dus in het voorbeeld hierboven:  bijvoorbeeld de 8 “grenst” aan de 9 en de 1 “grenst” dan aan de 2 etc.)
  • Als het vakje al gevuld is ga je één plaats recht naar onderen

Op die manier vul je, als het ware, de diagonalen.

Deze methode werkt voor iedere n×n-vierkant waarbij n oneven is.

Wanneer je gebruik maakt van opeenvolgende getallen vanaf 1 om een magisch vierkant te vullen dan is de magische som:

\frac{\sum\limits_{k=1}^{n}k}{\sqrt{n}}

Oftewel: Je telt alle getallen van 1 t/m n op en deelt deze som dan door de wortel van n.

Voor een 3×3-vierkant krijg je dan: 45/3=9 en voor het 4×4-vierkant: 136/4=34.

Een magisch vierkant is verder roteerbaar (90°, 180° ,270°) en  horizontaal, verticaal en diagonaal spiegelbaar:

Op het internet zijn heel veel sites over magische vierkanten te vinden. Zo zijn er ook generieke methodes voor een n×n vierkant waarbij n een viervoud is en generieke methodes voor een n×n vierkant met n even maar niet deelbaar door 4.

Alles bij elkaar

En nu komt alles bij elkaar!

Het spelen van het spelletje 15-en is in feite niets anders dan het spelen van Boter-Kaas-en-Eieren met het magische vierkant.

Er bestaat geen winnende strategie voor BKE en dus ook niet voor 15-en.

Er bestaat echter wel een strategie waarbij je nooit hoeft te verliezen, maar die laat ik graag aan de lezer dezes over…

Als je tegenstander niet zo sterk speelt dan zou je het volgende eens bij BKE kunnen proberen:

Als je als eerste aan de beurt bent zet dan je X in de linker bovenhoek. Je tegenspeler hoeft nog niet defensief te spelen en kan zijn O dus overal neerzetten (behalve linksboven). Als je tegenspeler de O niet in het midden zet probeer dan jouw X in een ander hoekpunt te plaatsen, zo ver mogelijk weg van de O.
Herhaal dat nogmaals zodat je 3 hoekpunten bezet krijgt. Nu moet je tegenspeler wel defensief spelen, want je kunt nu wellicht een horizontale rij of een verticale kolom vullen. Maar je kunt tegelijkertijd ook één van de diagonalen vullen, zodat je bij de volgende beurt altijd zult winnen!

Deze strategie zie je op de eerste afbeelding van dit artikel.

Ter herinnering:

Zo’n zelfde strategie kun je nu ook bij 15-en toepassen:

Analoog aan de BKE-strategie:

Kies 8, tegenspeler kiest (bv.) 2. Kies 4, tegenspeler kiest 3. Kies 6, tegenspeler kiest 1. Kies 5 en je kunt nu 15 maken met 4, 5 en 6.

Dus je speelt 15-en als BKE met het magische vierkant.