2023-03-26 23:33:41 -04:00
var _a ;
import { Chess , Ox88 , DEFAULT _POSITION , SQUARES , rank , file , WHITE , BLACK , KING , } from "./chess.js" ;
function with _move ( board , move ) {
let new _board = new Chess ( board . fen ( ) ) ;
new _board . move ( move ) ;
return new _board ;
}
// function max_move_by(board: Chess, score: (_: Chess) => number): string {
// let best_score = 0;
// let best_move = "";
// for (const move in board.moves()) {
// let new_board = with_move(board, move);
// let new_score = score(new_board);
// if (
// new_score > best_score ||
// (new_score == new_score && Math.random() > 0.5)
// ) {
// best_score = new_score;
// best_move = move;
// }
// }
// return best_move;
// }
// could return early if zero but meh
function min _move _by ( board , score ) {
let best _score = Infinity ;
let best _move = "" ;
let moves = board . moves ( ) ;
// console.log(moves);
for ( const i in moves ) {
let move = moves [ i ] ;
try {
let new _board = with _move ( board , move ) ;
let new _score = score ( new _board ) ;
// console.log({ move, best_score, new_score, best_move });
if ( new _score < best _score ||
( new _score == best _score && Math . random ( ) > 0.5 ) ) {
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 ;
}
// D --> movelist users (bad name whatever)
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 ] ;
} ;
// i believe the actual one tracks things better (pawns are treated as different)
// yeah this version sucks butttttt uh
// yeah
// prettier-ignore
var equalizer _state = {
piece _moves : {
p : 0 ,
b : 0 ,
n : 0 ,
r : 0 ,
q : 0 ,
k : 0 ,
} ,
// bad
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
}
} ;
const equalizer = board => {
let best _move ;
let least _moved _found = Infinity ;
let least _visited _found = Infinity ;
let piece _moving = "k" ;
let square _visiting = "a1" ;
// let least_moved = PIECE_SYMBOLS.sort(
// (a, b) =>
// equalizer_state.piece_moves[a] - equalizer_state.piece_moves[b]
// )[0];
// let least_visited = SQUARES.sort(
// (a, b) =>
// equalizer_state.square_visits[a] - equalizer_state.square_visits[b]
// )[0];
// cbb to do the proper randomness (23:41)
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 ;
} ;
// D --> min-maxxing some score of the board after moving
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 ) ;
// forgot the abs UGH
return Math . max ( Math . abs ( r2 - r1 ) , Math . abs ( f2 - f1 ) ) ;
}
const START _BOARD = new Chess ( DEFAULT _POSITION ) ;
// good enough
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" :
// inauthentic
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 ) ) ;
}
}
// oh gosh i can do symmetry by making the "distance" just a score fn which
// would... work but dang so many things have done something similar in hindsight
// actually hm symmetry is weird
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 ) ;
// or lens :]
function mirror _player ( mirror _fn ) {
let player _score = ( b ) => {
// it's worrying how the strategies have shifted, before it was more
// common for them to weigh your pieces equally, meaning they might take
// them to make the score better; silver lining: a bit easier to tell
// apart potentially
// the penalties are taken from the og code (the last four are the ones
// where i finally figured out where to find the players (curse_you
// UpperCamelCase))
// the behavior is likely still slightly different
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" ;
// vim's <C-a> was very helpful here :]
// I WAS MIRRORING AND THEN MIRRORING AGAIN OH MY GOOOOOSH
// prettier-ignore
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 ;
}
// prettier-ignore
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 ;
}
// maybe a find function would be useful Piece -> Board -> Square | 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 ) ;
} ;
// cccp and pacifist are basically opposites
// ugh not really
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 ;
}
// see generous in http://tom7.org/chess/weak.pdf
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" :
// impossible (mostly)
// idk why i said this it's really not impossible lol
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 ) ;
}
// hm
const HUMAN _COLOR = WHITE ;
const ROBOT _COLOR = BLACK ;
// DONE: I THINK: UGH piece value
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 ;
}
// maximize the difference in value
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 ) ) ;
}
// sum the ranks of the robot pieces (brittle with piece colors changing which uhh just don't do that :))
// they are zero based oopsie
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 ) ;
} ;
// FIXME: function name is TOO short FIX
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 ) ;
// value only goes down..... pretty much
// but the negation is now done by the guys so
sum += prev _value - new _value ;
}
return sum ;
}
// surprisingly not too difficult
const generous = board => {
// sum of the difference in ROBOT_COLOR piece value of all moves
// but like uhh negative
let generous _score = ( b ) => {
return - sum _of _possible _capture _value ( b ) ;
} ;
return min _move _by ( board , generous _score ) ;
} ;
const no _i _insist = board => {
let 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 ) ;
} ;
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 : 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" ,
} ,
// alphabetical is fine i guess, since the checklist order has little to do with the og paper
// ehhh nvm
] ; //.sort((a, b) => a.name.localeCompare(b.name));
// https://stackoverflow.com/questions/12739171/javascript-epoch-time-in-days
function days _since _epoch ( ) {
var now = new Date ( ) . getMilliseconds ( ) ;
return Math . floor ( now / 8.64 e7 ) ;
}
function find _specified _in _hash ( ) {
return players . find ( p => ` # ${ p . name } ` == window . location . hash ) ;
}
// bad
// const todays_player = players[days_since_epoch() % players.length];
const todays _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 ${ todays _player . name } ` ) ;
// const todays_player = min_oppt_move;
const board = document . getElementById ( "board" ) ;
const game = new Chess ( ) ;
function computer _move ( ) {
num _moves ++ ;
return todays _player . f ( game ) ;
}
// might want to change this when it's more wordly
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 show _win ( ) {
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" ;
2023-03-27 21:27:54 -04:00
let stats _text = ` #EloWorldle \n #ew_ ${ todays _player . name } \n Guesses: ${ num _guesses } \n Moves: ${ num _moves } ` ;
2023-03-26 23:33:41 -04:00
stats . innerText = stats _text ;
let copy _stats = document . getElementById ( "copy-stats" ) ;
copy _stats . style . display = "block" ;
copy _stats . addEventListener ( "click" , _ => navigator . clipboard . writeText ( stats _text ) ) ;
console . log ( "PLAYER IS WIN" ) ;
}
var num _guesses = 0 ;
var num _moves = 0 ;
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 ++ ;
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 ) ;
}
}
window . addEventListener ( "DOMContentLoaded" , ( ) => {
board . addEventListener ( "movestart" , ( e ) => {
console . log ( ` Move started: ${ e . detail . from } , ${ e . detail . piece . color } ${ e . detail . piece . pieceType } ` ) ;
e . detail . setTargets (
// This produces a list like ["e3", "e5"]
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 ( ) ) ;
append _players _to ( document . getElementById ( "players" ) ) ;
} ) ;