elo-worldle/main.min.js
2023-04-01 23:37:30 -04:00

7 lines
16 KiB
JavaScript

import{Chess,Ox88,DEFAULT_POSITION,SQUARES,rank,file,WHITE,BLACK,KING}from"./chess.min.js";function with_move(board,move){let new_board=new Chess(board.fen());new_board.move(move);return new_board}function shuffle(array){let currentIndex=array.length,randomIndex;while(currentIndex!=0){randomIndex=Math.floor(Math.random()*currentIndex);currentIndex--;[array[currentIndex],array[randomIndex]]=[array[randomIndex],array[currentIndex]]}return array}function min_move_by(board,score){let best_score=Infinity;let best_move="";let moves=board.moves();shuffle(moves);for(const move of moves){try{let new_board=with_move(board,move);let new_score=score(new_board);if(new_score<best_score){console.log("New best (or equal): ",new_score,move);best_score=new_score;best_move=move}}catch(e){console.error(e);continue}}return best_move}const random_move=board=>{const moves=board.moves();const move=moves[Math.floor(Math.random()*moves.length)];return move};function make_lexo(m){let{rank:src_row,file:src_col}=rank_and_file(m.from);let{rank:dst_row,file:dst_col}=rank_and_file(m.to);let promote_to=m.promotion;return[src_row,src_col,dst_row,dst_col,promote_to]}function lexo_sort_moves(a,b){let al=make_lexo(a);let bl=make_lexo(a);for(const i in al){if(al[i]<bl[i]){return-1}else if(al[i]>bl[i]){return 1}}return 0}const first_move=board=>{const moves=board.moves({verbose:true}).sort(lexo_sort_moves);return moves[0].san};const alphabetical=board=>{const moves=board.moves();moves.sort();return moves[0]};const equalizer_state_default={piece_moves:{p:0,b:0,n:0,r:0,q:0,k:0},square_visits:{a8:0,b8:0,c8:0,d8:0,e8:0,f8:0,g8:0,h8:0,a7:0,b7:0,c7:0,d7:0,e7:0,f7:0,g7:0,h7:0,a6:0,b6:0,c6:0,d6:0,e6:0,f6:0,g6:0,h6:0,a5:0,b5:0,c5:0,d5:0,e5:0,f5:0,g5:0,h5:0,a4:0,b4:0,c4:0,d4:0,e4:0,f4:0,g4:0,h4:0,a3:0,b3:0,c3:0,d3:0,e3:0,f3:0,g3:0,h3:0,a2:0,b2:0,c2:0,d2:0,e2:0,f2:0,g2:0,h2:0,a1:0,b1:0,c1:0,d1:0,e1:0,f1:0,g1:0,h1:0}};var equalizer_state=equalizer_state_default;const equalizer=board=>{let best_move;let least_moved_found=Infinity;let least_visited_found=Infinity;let piece_moving="k";let square_visiting="a1";for(const move of board.moves({verbose:true})){if(equalizer_state.piece_moves[move.piece]<least_moved_found||equalizer_state.piece_moves[move.piece]==least_moved_found&&equalizer_state.square_visits[move.to]<least_visited_found){best_move=move;least_moved_found=equalizer_state.piece_moves[move.piece];least_visited_found=equalizer_state.square_visits[move.to];piece_moving=move.piece;square_visiting=move.to}}equalizer_state.piece_moves[piece_moving]+=1;equalizer_state.square_visits[square_visiting]+=1;return best_move.san};const min_oppt_move=board=>{let num_moves=b=>b.moves().length;return min_move_by(board,num_moves)};function num_pieces_on_own_color(b){let count=0;for(const square of SQUARES){let piece=b.get(square);if(typeof piece=="boolean"){continue}if(piece.color==b.squareColor(square)){count+=1}}return count}const same_color=board=>{return min_move_by(board,b=>64-num_pieces_on_own_color(b))};const opposite_color=board=>{return min_move_by(board,num_pieces_on_own_color)};function rank_and_file(s){return{rank:rank(Ox88[s]),file:file(Ox88[s])}}function chebyshev_distance(a,b){let{rank:r1,file:f1}=rank_and_file(a);let{rank:r2,file:f2}=rank_and_file(b);return Math.max(Math.abs(r2-r1),Math.abs(f2-f1))}const START_BOARD=new Chess(DEFAULT_POSITION);function move_distance(piece){let rank=rank_and_file(piece.square).rank;let file=rank_and_file(piece.square).file;switch(piece.type){case"k":return chebyshev_distance(piece.square,find({color:HUMAN_COLOR,type:"k"},START_BOARD));case"p":return Math.abs(rank-6);case"r":return Math.abs(rank-7)+Math.min(Math.abs(file-7),Math.abs(file-0));case"n":return pieces_of_color(START_BOARD,HUMAN_COLOR).filter(p=>p.type=="k").map(p=>chebyshev_distance(piece.square,p.square)).reduce((a,b)=>Math.max(a,b));case"b":let is_black=START_BOARD.squareColor(piece.square)=="b";let dest_col=is_black?2:5;let dr=Math.abs(rank-7);let dc=Math.abs(file-dest_col);return Math.max(dr,dc);case"q":return Math.max(Math.abs(rank-7),Math.abs(file-3))}}function minimize_distances(distance_fn){const player_score=board=>{let total_distance=0;for(const piece of pieces_of_color(board,ROBOT_COLOR)){total_distance+=distance_fn(piece)}return total_distance};return b=>min_move_by(b,player_score)}const reverse_starting=minimize_distances(move_distance);function mirror_player(mirror_fn){let player_score=b=>{let total=0;for(const square of SQUARES){let reflected=mirror_fn(square);console.log(square,reflected);let piece=b.get(square);let reflection=b.get(reflected);if(typeof piece=="boolean"&&typeof reflection=="boolean"){continue}if(typeof piece=="boolean"){total+=10;continue}if(typeof reflection=="boolean"){total+=10;continue}if(piece.color==reflection.color){total+=5;continue}if(piece.type!=reflection.type){total+=1;continue}}return total};return b=>min_move_by(b,player_score)}function rank_and_file_to_algebraic(rf){let r="8";let f="h";switch(rf.rank){case 0:r="8";break;case 1:r="7";break;case 2:r="6";break;case 3:r="5";break;case 4:r="4";break;case 5:r="3";break;case 6:r="7";break;case 7:r="1";break}switch(rf.file){case 0:f="a";break;case 1:f="b";break;case 2:f="c";break;case 3:f="d";break;case 4:f="e";break;case 5:f="f";break;case 6:f="g";break;case 7:f="h";break}return`${f}${r}`}function mirror_y(s){let rf=rank_and_file(s);rf.rank=7-rf.rank;return rank_and_file_to_algebraic(rf)}const sym_mirror_y=mirror_player(mirror_y);function mirror_x(s){let rf=rank_and_file(s);rf.file=7-rf.file;return rank_and_file_to_algebraic(rf)}const sym_mirror_x=mirror_player(mirror_x);function flip(s){let rf=rank_and_file(s);rf.rank=7-rf.rank;rf.file=7-rf.file;return rank_and_file_to_algebraic(rf)}const sym_180=mirror_player(flip);function find(p,b){for(const square of SQUARES){let got=b.get(square);if(typeof got=="boolean"){continue}if(got.color==p.color&&got.type==p.type){return square}}console.log(`didn't find this :O ${p}`);return null}const suicide_king=board=>{let distance_between_kings=b=>{var _a,_b;return chebyshev_distance((_a=find({color:WHITE,type:KING},b))!==null&&_a!==void 0?_a:"a1",(_b=find({color:BLACK,type:KING},b))!==null&&_b!==void 0?_b:"a1")};return min_move_by(board,distance_between_kings)};function pieces_of_color(b,color){let flat_board=b.board().flat();let non_empties=[];for(const s of flat_board){if(typeof s=="boolean"){continue}non_empties.push(s)}return non_empties.filter(p=>p.color==color)}function num_pieces_of_color(b,color){return pieces_of_color(b,color).length}function value_piece(type){switch(type){case"p":return 1;case"b":case"n":return 3;case"r":return 5;case"q":return 9;case"k":return 413}}function value_of_pieces_of_color(b,color){return pieces_of_color(b,color).map(p=>value_piece(p.type)).reduce((a,b)=>a+b)}const HUMAN_COLOR=WHITE;const ROBOT_COLOR=BLACK;const cccp=board=>{let cccp_score=b=>{if(b.isCheckmate()){console.log("Found checkmate! :D");return 0}if(b.isCheck()){console.log("Found check! :T");return 1}let prev_value=value_of_pieces_of_color(board,HUMAN_COLOR);let new_value=value_of_pieces_of_color(b,HUMAN_COLOR);if(new_value<prev_value){console.log("There's less human-colored pieces now! :)");return 10*(6-(prev_value-new_value))}return 100*pieces_of_color(b,ROBOT_COLOR).map(b=>8-rank_and_file(b.square).rank).reduce((a,b)=>a+b)};return min_move_by(board,cccp_score)};function total_chebyshev_distances_of_pieces_of_color_to_square(board,color,square){return pieces_of_color(board,color).map(p=>chebyshev_distance(p.square,square)).reduce((a,b)=>a+b)}const huddle=board=>{return min_move_by(board,b=>{var _a;return total_chebyshev_distances_of_pieces_of_color_to_square(b,ROBOT_COLOR,(_a=find({color:ROBOT_COLOR,type:KING},b))!==null&&_a!==void 0?_a:"a1")})};const swarm=board=>{return min_move_by(board,b=>{var _a;return total_chebyshev_distances_of_pieces_of_color_to_square(b,ROBOT_COLOR,(_a=find({color:HUMAN_COLOR,type:KING},b))!==null&&_a!==void 0?_a:"a1")})};function sum_of_possible_capture_value(b){let prev_value=value_of_pieces_of_color(b,ROBOT_COLOR);let sum=0;for(const move of b.moves()){let new_board=with_move(b,move);let new_value=value_of_pieces_of_color(new_board,ROBOT_COLOR);sum+=prev_value-new_value}return sum}const generous=board=>{let generous_score=b=>{return-sum_of_possible_capture_value(b)};return min_move_by(board,generous_score)};const coward=board=>min_move_by(board,sum_of_possible_capture_value);function no_i_insist_score(b){if(b.isCheckmate()){return 612}if(b.isCheck()){return 413}let prev_value=value_of_pieces_of_color(b,ROBOT_COLOR);return-(sum_of_possible_capture_value(b)/b.moves().length)}const no_i_insist=board=>{return min_move_by(board,no_i_insist_score)};const pacifist=board=>{let pacifist_score=b=>{if(b.isCheckmate()){return 612}if(b.isCheck()){return 413}let prev_value=value_of_pieces_of_color(board,HUMAN_COLOR);let new_value=value_of_pieces_of_color(b,HUMAN_COLOR);if(new_value<prev_value){return prev_value-new_value}return 0};return min_move_by(board,pacifist_score)};const players=[{f:random_move,name:"random_move",description:"Moves randomly, believe it or not."},{f:same_color,name:"same_color",description:"Makes the move that maximizes the number of pieces on their own color"},{f:opposite_color,name:"opposite_color",description:"Behavior left as an exercise to the reader"},{f:pacifist,name:"pacifist",description:"Avoids checkmate, then avoids checks, then avoids captures, if forced, captures the lowest value piece it can"},{f:first_move,name:"first_move",description:"Makes the lexicographically first move based on [source_row, source_col, dest_row, dest_col, promotion]"},{f:alphabetical,name:"alphabetical",description:"Makes the first move asciilphabetically (based on algebraic notation)"},{f:huddle,name:"huddle",description:"Makes the move that minimizes the total Chebyshev distances from their pieces to their king"},{f:swarm,name:"swarm",description:"Makes the move that minimizes the total Chebyshev distances from their pieces to YOUR king"},{f:generous,name:"generous",description:"Maximizes the number of YOUR moves that take their pieces, weighting by value"},{f:no_i_insist,name:"no_i_insist",description:"Maximizes the proportion of potential capture value of YOUR moves to the number of YOUR moves; very weird the way i've done it"},{f:coward,name:"coward",description:"Surprisingly uncowardly, tries to avoid losing pieces, weighted by value"},{f:reverse_starting,name:"reverse_starting",description:"Makes the move that minimizes the number of moves it takes to move their pieces to the opposite end"},{f:cccp,name:"cccp",description:"First checkmates, then checks, then captures (the best piece it can), then pushes"},{f:suicide_king,name:"suicide_king",description:"Makes the move that minimizes the Chebyshev distance between the two kings"},{f:sym_mirror_y,name:"sym_mirror_y",description:"Tries to make the board as vertically symmetric as possible (basically copies your moves)"},{f:sym_mirror_x,name:"sym_mirror_x",description:"Tries to make the board as horizontally symmetric as possible (basically doesn't copy your moves or do anything useful)"},{f:sym_180,name:"sym_180",description:"Tries to make the board as symmetric as possible when flipped around"},{f:min_oppt_move,name:"min_oppt_move",description:"Does the move that leaves the least moves for YOU"},{f:equalizer,name:"equalizer",description:"Moves the least moved piece (actually piece type, e.g. moving a pawn counts for all pawns; it sucks, i know), breaking ties by moving to the least visited square"}];function days_since_epoch(){var now=(new Date).getMilliseconds();return Math.floor(now/864e5)}function find_specified_in_hash(){return players.find(p=>`#${p.name}`==window.location.hash)}function get_todays_player(){var _a;let player=(_a=find_specified_in_hash())!==null&&_a!==void 0?_a:players[Math.floor(Math.random()*players.length)];console.log(`SPOILER: "today"'s player is ${player.name}`);return player}var todays_player=get_todays_player();var board=document.getElementById("board");var game=new Chess;function computer_move(){num_moves++;history+="M";return todays_player.f(game)}function show_game_over(){let game_over=document.getElementById("game-over");game_over.style.display="block";game_over.innerText=`GAME OVER; player was ${todays_player.name}`;console.log("GAME OVER")}function copy_stats_to_clipboard(){navigator.clipboard.writeText(document.getElementById("stats").innerText)}function show_win(){wins++;in_game_over=true;let win_count=document.getElementById("win-count");win_count.style.display="block";win_count.innerText=`You have won ${wins} time${wins==1?"":"s"} without refreshing${wins_exclamation_marks()}`;let game_over=document.getElementById("game-over");game_over.style.display="block";game_over.innerText="YOU WIN YOU WIN YOU WIN YOU WIN HOLY CRAP";let stats=document.getElementById("stats");stats.style.display="block";let stats_text=`#elo_worldle
#ew_${todays_player.name}
Win #${wins}
Guesses: ${num_guesses}
Moves: ${num_moves}
${history}`;stats.innerText=stats_text;let copy_stats=document.getElementById("copy-stats");copy_stats.style.display="block";copy_stats.addEventListener("click",_=>copy_stats_to_clipboard());let play_again=document.getElementById("play-again");play_again.style.display="block";play_again.addEventListener("click",_=>reset());console.log("PLAYER IS WIN")}var num_guesses=0;var num_moves=0;var history="";function append_players_to(n){for(const player of players){let child=document.createElement("div");child.classList.add("player");let child_name=document.createElement("h1");child_name.classList.add("player-name");child_name.innerText=player.name;let child_desc=document.createElement("p");child_desc.classList.add("player-desc");child_desc.innerText=player.description;let guess_button=document.createElement("button");guess_button.innerText="^ guess ^";guess_button.onclick=_=>{num_guesses++;history+="G";if(todays_player.name==player.name){show_win();child.classList.add("correct")}else{child.classList.add("wrong")}guess_button.onclick=()=>{}};child.appendChild(child_name);child.appendChild(child_desc);child.appendChild(guess_button);child_name.addEventListener("click",_=>child.classList.toggle("strikethrough"));n.appendChild(child)}}var wins=0;function wins_exclamation_marks(){let base_10_log_of_wins=Math.floor(Math.log10(wins));return base_10_log_of_wins>0?"!".repeat(base_10_log_of_wins):"."}var in_game_over=false;function reset(){let play_again=document.getElementById("play-again");play_again.style.display="none";play_again.onclick=_=>console.log("you can't play again again sorry :]");in_game_over=false;todays_player=get_todays_player();game=new Chess;board.fen=game.fen();board.turn="white";board.interactive=true;num_guesses=0;num_moves=0;history="";equalizer_state=equalizer_state_default;let players=document.getElementById("players");players.innerHTML="";append_players_to(players);let stats=document.getElementById("stats");stats.innerHTML="";stats.style.display="none";document.getElementById("copy-stats").style.display="none";document.getElementById("game-over").style.display="none"}window.addEventListener("DOMContentLoaded",()=>{document.getElementById("dummy-board").style.display="none";document.getElementById("players").classList.remove("much-tall");board.addEventListener("movestart",e=>{console.log(`Move started: ${e.detail.from}, ${e.detail.piece.color} ${e.detail.piece.pieceType}`);e.detail.setTargets(game.moves({square:e.detail.from,verbose:true}).map(m=>m.to));if(game.isGameOver()){show_game_over()}});board.addEventListener("moveend",e=>{console.log(`Move ending: ${e.detail.from} -> ${e.detail.to}, ${e.detail.piece.color} ${e.detail.piece.pieceType}`);const move=game.move({from:e.detail.from,to:e.detail.to,promotion:"q"});if(move===null){e.preventDefault()}if(game.isGameOver()){show_game_over()}});board.addEventListener("movefinished",e=>{board.fen=game.fen();board.turn=game.turn()==="w"?"white":"black";board.interactive=game.turn()==="w";if(!board.interactive){let move=computer_move();if(!move){console.log("Didn't get a move from the computer (this is fine if the game is over)");return}console.log(`Computer move: ${move}`);game.move(move);board.fen=game.fen();board.turn="white";board.interactive=true}});document.getElementById("finish").addEventListener("click",_=>show_game_over());document.addEventListener("keypress",e=>{if(e.key=="r"&&in_game_over){reset()}else if(e.key=="c"&&in_game_over){copy_stats_to_clipboard()}});append_players_to(document.getElementById("players"))});