/*************************************/ /* MENACE */ /* Machine Educable Noughts */ /* and Crosses Engine */ /*************************************/ /* Based on the first MENACE built */ /* by Donald Michie in 1960 using */ /* 304 matchboxes. */ /*************************************/ /* This implementation was written */ /* by Matthew Scroggs */ /* https://www.mscroggs.co.uk/menace */ /*************************************/ // MENACEs var menace = { 1:{ "boxes":{}, "orderedBoxes":[], "start":[8,4,2,1], "removesymm":true, "incentives":[1,3,-1], "moves":[], "player":1}, 2:{ "boxes":{}, "orderedBoxes":[], "start":[8,4,2,1], "removesymm":true, "incentives":[1,3,-1], "moves":[], "player":2} } // what is player 2? var player = 'h' document.getElementById("p2picker").value = "h" document.getElementById("speeddiv").style.display = "none" var whoA = {"h":"Human", "r":"Random", "m":"MENACE2", "p":"Perfect"} // plotting var plotdata = [0] var xmin = 0 var xmax = 0 var ymin = 0 var ymax = 0 // game data var playagain = true var wins_each = [0,0,0] var board = [0,0,0,0,0,0,0,0,0] var no_winner = true var pieces = ["","◯","×"] var said = ["","","","","","","","","",""] var human_turn=false var pwns = [ [0,1,2], [3,4,5], [6,7,8], [0,3,6], [1,4,7], [2,5,8], [0,4,8], [6,4,2] ] var rotations=[ [0,1,2,3,4,5,6,7,8], [0,3,6,1,4,7,2,5,8], [6,3,0,7,4,1,8,5,2], [6,7,8,3,4,5,0,1,2], [8,7,6,5,4,3,2,1,0], [8,5,2,7,4,1,6,3,0], [2,5,8,1,4,7,0,3,6], [2,1,0,5,4,3,8,7,6] ] // array utility functions function arrmin(arr){ var out = arr[0] for(var i=1;i" } play_menace() } } function setPlayer(setTo){ player = setTo document.getElementById("who").innerHTML = whoA[setTo] if(setTo=="m"){ show_menace(2) } else { hide_menace(2) } if(setTo!="h"){ document.getElementById("speeddiv").style.display = "block" } else { document.getElementById("speeddiv").style.display = "none" } if(setTo!="h" && human_turn){ play_opponent() } } function winner(b){ var pos = b.join("") var th = three(pos) if(th != 0){ return th } if(count(b,0) == 0){ return 0 } return false } function opposite_result(r){ if(r==0){ return 0 } return 3-r } function check_win(){ var who_wins = winner(board) if(who_wins !== false){ if(who_wins == 0){ say("It's a draw.") } if(who_wins == 1){ say("MENACE wins.") } if(who_wins == 2){ say(whoA[player]+" wins.") } do_win(who_wins) human_turn = false } } function do_win(who_wins){ no_winner = false for(var i=0;i<9;i++){ if(board[i] == 0){ document.getElementById("pos"+i).innerHTML = "" } } menace_add_beads(who_wins) if(player == "h"){ window.setTimeout(new_game, 1000) } else { window.setTimeout(new_game, -parseInt(document.getElementById("speed_slider").value)) } } function play_menace(){ where = get_menace_move(1) if(where=="resign"){ if(count(board,0)==9){ say("MENACE has run out of beads in the first box and refuses to play.") playagain = false return } do_win(2) say("MENACE resigns") return } board[where] = 1 document.getElementById("pos"+where).innerHTML = pieces[1] check_win() if(no_winner){ play_opponent() } } function play_opponent(){ if(player == 'h'){ human_turn = true return } human_turn = false var where = undefined if(player == 'r'){ where = get_random_move() } else if(player == 'm'){ where = get_menace_move(2) } else if(player == 'p'){ where = get_perfect_move() } if(where=="resign"){ do_win(1) say("MENACE2 resigns") return } board[where] = 2 document.getElementById("pos"+where).innerHTML = pieces[2] check_win() if(no_winner){ window.setTimeout(play_menace, -parseInt(document.getElementById("speed_slider").value)/10) } } // board functions function apply_rotation(pos,rot){ var new_pos = "" for(var j=0;j<9;j++){ new_pos += pos[rot[j]] } return new_pos } function find_all_rotations(pos){ var max = -1 var max_rot = [] for(var i=0;i max){ max = try_pos max_rot = [] } if(try_pos == max){ max_rot.push(i) } } return max_rot } function find_rotation(pos){ var max_rot = find_all_rotations(pos) return max_rot[Math.floor(Math.random()*max_rot.length)] } function three(pos){ for(var i=0;i=0;i--){ if(b[i] == move){ minmove = i } } for(var i=0;i") } function make_ox(pos,n){ var output = "
" for(var i=0;i<9;i++){ if(i%3 == 0){output+=""} output += "" } else { output += "'>" output += pieces[pos[i]] output += "" } if(i%3 == 2){output+=""} } output += "
"+menace[n]["boxes"][pos][i]+"
" return output } function show_set(n){ if(n==1){ document.getElementById("im1").value = menace[1]["start"][0] document.getElementById("im3").value = menace[1]["start"][1] document.getElementById("im5").value = menace[1]["start"][2] document.getElementById("im7").value = menace[1]["start"][3] } if(n==2){ document.getElementById("im2").value = menace[2]["start"][0] document.getElementById("im4").value = menace[2]["start"][1] document.getElementById("im6").value = menace[2]["start"][2] document.getElementById("im8").value = menace[2]["start"][3] } document.getElementById("_"+n+"_ic_w").value = menace[n]["incentives"][1] document.getElementById("_"+n+"_ic_d").value = menace[n]["incentives"][0] document.getElementById("_"+n+"_ic_l").value = -menace[n]["incentives"][2] document.getElementById("_"+n+"_includeall").checked = menace[n]["removesymm"] document.getElementById("_"+n+"_tweak_h").style.display = "block" document.getElementById("_"+n+"_tweak_s").style.display = "none" } function hide_set(n){ document.getElementById("_"+n+"_tweak_h").style.display = "none" document.getElementById("_"+n+"_tweak_s").style.display = "block" } function show_menace(n){ var menacename = "MENACE" if(n==2){ menacename += "2" } var output = "" output += "
▼ Show "+menacename+"'s settings ▼
" output += "" output += "" output += "" output += "
"; var boxout = "" var numb = 0 for(var move=0;move"+make_ox(key,n)+"" if(cols == 7){ boxout += "" cols = 0 } } if(cols != 0){ boxout += "" } boxout += "

" } output += "This box shows all " + numb + " matchboxes that make up "+menacename+".

" output += boxout output += "

"; document.getElementById("_"+n+"_moves").innerHTML = output } function hide_menace(n){ document.getElementById("_"+n+"_moves").innerHTML = "" } // opponent moves function get_random_move(){ choices = [] for(var i=0;i<9;i++){ if(board[i] == 0){ choices.push(i) } } return choices[Math.floor(Math.random()*choices.length)] } function get_perfect_move(){ return minimax(board,2).index } function play_human(where){ if(no_winner){ human_turn = false board[where] = 2 document.getElementById("pos"+where).innerHTML = pieces[2] check_win() if(no_winner){ play_menace() } } } function make_move(plays){ total = 0 for(var i=0;i bestScore) || (player == 1 && moves[i].score < bestScore)) { bestScore = moves[i].score bestMove = i } } var bestMoves = [] for(var i=0;i