795 lines
26 KiB
JavaScript
795 lines
26 KiB
JavaScript
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;
|
|
// }
|
|
// classic: https://stackoverflow.com/questions/2450954/how-to-randomize-shuffle-a-javascript-array
|
|
function shuffle(array) {
|
|
let currentIndex = array.length, randomIndex;
|
|
// While there remain elements to shuffle.
|
|
while (currentIndex != 0) {
|
|
// Pick a remaining element.
|
|
randomIndex = Math.floor(Math.random() * currentIndex);
|
|
currentIndex--;
|
|
// And swap it with the current element.
|
|
[array[currentIndex], array[randomIndex]] = [
|
|
array[randomIndex],
|
|
array[currentIndex],
|
|
];
|
|
}
|
|
return array;
|
|
}
|
|
// could return early if zero but meh
|
|
function min_move_by(board, score) {
|
|
let best_score = Infinity;
|
|
let best_move = "";
|
|
let moves = board.moves();
|
|
shuffle(moves);
|
|
// console.log(moves);
|
|
for (const move of moves) {
|
|
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
|
|
// array is already shuffled
|
|
// (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
|
|
const equalizer_state_default = {
|
|
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
|
|
}
|
|
};
|
|
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";
|
|
// 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 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",
|
|
},
|
|
// 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.64e7);
|
|
}
|
|
function find_specified_in_hash() {
|
|
return players.find(p => `#${p.name}` == window.location.hash);
|
|
}
|
|
// bad
|
|
// const todays_player = players[days_since_epoch() % players.length];
|
|
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();
|
|
// const todays_player = min_oppt_move;
|
|
var board = document.getElementById("board");
|
|
var game = new Chess();
|
|
function computer_move() {
|
|
num_moves++;
|
|
history += "M";
|
|
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 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;
|
|
// extraordinarily necessary
|
|
function wins_exclamation_marks() {
|
|
let wins_divided_by_ten = Math.floor(wins / 10);
|
|
return wins_divided_by_ten > 0 ? "!".repeat(wins_divided_by_ten) : ".";
|
|
}
|
|
var in_game_over = false;
|
|
// ERROR: !
|
|
// INELEGANT
|
|
// "HACK"!
|
|
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", () => {
|
|
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());
|
|
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"));
|
|
});
|