---
# System prepended metadata

title: Source code

---


## Практическая работа #5 Шахматы 
![image](https://hackmd.io/_uploads/ByZiHIucbe.png)


### Интерфейс (`chess.html`)

Страница разделена на две части:

слева находится сама доска, справа - информация (чей ход, кнопка новой игры и журнал ходов).

Доска сделана через grid 8×8. Клетки чередуются по цвету (светлые/тёмные). Также добавлены координаты (буквы a–h и цифры 1–8), чтобы было удобнее ориентироваться.

Справа:

* показывается текущий ход (белые или чёрные);
* есть кнопка «Новая игра»;
* есть журнал ходов, куда записываются действия (например: ♘g1-f3).

### Логика (`chess.js`)

Используется ООП.

Есть базовый класс `Piece`, в котором хранятся:
цвет, тип фигуры, позиция и флаг (ходила ли фигура).

От него наследуются все фигуры:
`Pawn`, `Rook`, `Knight`, `Bishop`, `Queen`, `King`.

У каждой фигуры есть метод `getMoves()`, который возвращает возможные ходы по правилам:

* пешка ходит вперёд, с первой позиции может на 2 клетки, бьёт по диагонали;
* ладья - по прямым;
* слон - по диагоналям;
* ферзь - комбинация ладьи и слона;
* конь -Г»-образно (может перепрыгивать фигуры);
* король - на 1 клетку в любую сторону.

Есть основной класс `Game`, который управляет игрой:

* хранит доску (массив 8×8);
* хранит текущий ход;
* отвечает за выбор фигуры и перемещение;
* обновляет интерфейс.

Основные моменты:

* при клике по фигуре показываются возможные ходы;
* при клике по подсвеченной клетке фигура перемещается;
* если на клетке фигура противника — она «съедается»;
* после хода очередь передаётся другому игроку;
* каждый ход записывается в журнал.

### Что реализовано

* доска 8×8 с координатами;
* стандартная расстановка фигур;
* ходы по очереди (сначала белые);
* нельзя ходить чужими фигурами;
* подсветка ходов и атак;
* перемещение фигур;
* журнал ходов;
* кнопка сброса игры.

### Что не реализовано

Не делал:
шах, мат, рокировку, превращение пешки и т.д.


# Source code 

```html 
<!DOCTYPE html>
<html lang="ru">
<head>
  <meta charset="UTF-8">
  <title>DOM Chess</title>
  <meta name="viewport" content="width=device-width, initial-scale=1">
  <link rel="stylesheet" href="style.css">
  <style>
    .chess-layout {
      max-width: 960px;
      margin: 2rem auto;
      padding: 1.5rem;
      border-radius: 1.5rem;
      background: radial-gradient(circle at top left, rgba(37, 99, 235, 0.18), rgba(15, 23, 42, 0.95));
      border: 1px solid rgba(148, 163, 184, 0.7);
      display: grid;
      grid-template-columns: minmax(0, 2fr) minmax(0, 1.2fr);
      gap: 1.5rem;
    }
    .chess-board {
      position: relative;
      width: 100%;
      max-width: 600px;
      aspect-ratio: 1 / 1;
      margin: 0 auto;
      display: grid;
      grid-template-columns: repeat(8, 1fr);
      grid-template-rows: repeat(8, 1fr);
      border-radius: 0.9rem;
      overflow: hidden;
      box-shadow: 0 22px 60px rgba(15, 23, 42, 0.95);
    }
    .chess-cell {
      display: flex;
      align-items: center;
      justify-content: center;
      font-size: clamp(2rem, 3.5vw, 2.6rem);
      cursor: pointer;
      position: relative;
      transition: background 0.15s;
    }
    .chess-light {
      background: #f0d9b5;
    }
    .chess-dark {
      background: #b58863;
    }
    .chess-selected {
      outline: 3px solid #22c55e;
      outline-offset: -3px;
    }
    .chess-move {
      box-shadow: 0 0 0 6px rgba(34, 197, 94, 0.5) inset;
    }
    .chess-capture {
      box-shadow: 0 0 0 6px rgba(248, 113, 113, 0.8) inset;
    }
    .chess-coords {
      position: absolute;
      inset: 0;
      pointer-events: none;
      font-size: 0.7rem;
      color: rgba(15, 23, 42, 0.8);
    }
    .chess-coords-file {
      position: absolute;
      bottom: 4px;
      right: 4px;
    }
    .chess-coords-rank {
      position: absolute;
      top: 4px;
      left: 4px;
    }
    .chess-panel {
      display: flex;
      flex-direction: column;
      gap: 0.7rem;
    }
    .chess-panel h1 {
      margin: 0 0 0.5rem;
      font-size: 1.6rem;
    }
    .chess-info-line {
      font-size: 0.95rem;
    }
    .chess-turn {
      font-weight: 600;
      color: #bfdbfe;
    }
    .chess-controls {
      display: flex;
      gap: 0.75rem;
      flex-wrap: wrap;
      margin-top: 0.5rem;
    }
    .chess-log {
      min-height: 120px;
      max-height: 200px;
      overflow-y: auto;
      padding: 0.75rem;
      border-radius: 0.75rem;
      background: rgba(15, 23, 42, 0.9);
      border: 1px solid rgba(148, 163, 184, 0.7);
      font-size: 0.85rem;
    }
    .chess-log p {
      margin: 0 0 0.25rem;
    }
    .chess-legend {
      font-size: 0.85rem;
      color: #e5e7eb;
    }
    @media (max-width: 800px) {
      .chess-layout {
        grid-template-columns: 1fr;
      }
    }
  </style>
</head>
<body>
  <div class="chess-layout">
    <div>
      <div id="chessBoard" class="chess-board"></div>
    </div>
    <section class="chess-panel">
      <h1>DOM Chess</h1>
      <p class="chess-info-line">Текущий ход: <span id="chessTurn" class="chess-turn"></span></p>
      <p class="chess-info-line" id="chessStatus"></p>
      <div class="chess-controls">
        <button id="chessNewGame" class="btn-primary">Новая игра</button>
      </div>
      <div>
        <h2 style="font-size: 1rem; margin-bottom: 0.4rem;">Ходы</h2>
        <div id="chessLog" class="chess-log"></div>
      </div>
      <p class="chess-legend">
        Зелёная подсветка показывает клетки, куда фигура может пойти, красная — возможное взятие фигуры противника.
      </p>
    </section>
  </div>

  <script src="chess.js"></script>
</body>
</html>
```

```js 
const PIECE_SYMBOLS = {
  white: {
    king: "♔",
    queen: "♕",
    rook: "♖",
    bishop: "♗",
    knight: "♘",
    pawn: "♙"
  },
  black: {
    king: "♚",
    queen: "♛",
    rook: "♜",
    bishop: "♝",
    knight: "♞",
    pawn: "♟"
  }
};

class Piece {
  constructor(color, type, row, col) {
    this.color = color;
    this.type = type;
    this.row = row;
    this.col = col;
    this.hasMoved = false;
  }
  symbol() {
    return PIECE_SYMBOLS[this.color][this.type];
  }
  insideBoard(row, col) {
    return row >= 0 && col >= 0 && row < 8 && col < 8;
  }
  basicMoves(board, directions, repeat) {
    const moves = [];
    for (const [dr, dc] of directions) {
      let r = this.row + dr;
      let c = this.col + dc;
      while (this.insideBoard(r, c)) {
        const target = board[r][c];
        if (!target) {
          moves.push({ row: r, col: c });
        } else {
          if (target.color !== this.color) moves.push({ row: r, col: c });
          break;
        }
        if (!repeat) break;
        r += dr;
        c += dc;
      }
    }
    return moves;
  }
}

class Pawn extends Piece {
  getMoves(board) {
    const moves = [];
    const dir = this.color === "white" ? -1 : 1;
    const startRow = this.color === "white" ? 6 : 1;
    const frontRow = this.row + dir;
    if (this.insideBoard(frontRow, this.col) && !board[frontRow][this.col]) {
      moves.push({ row: frontRow, col: this.col });
      const twoRow = this.row + dir * 2;
      if (this.row === startRow && !board[twoRow][this.col]) {
        moves.push({ row: twoRow, col: this.col });
      }
    }
    for (const dc of [-1, 1]) {
      const r = this.row + dir;
      const c = this.col + dc;
      if (!this.insideBoard(r, c)) continue;
      const target = board[r][c];
      if (target && target.color !== this.color) {
        moves.push({ row: r, col: c });
      }
    }
    return moves;
  }
}

class Rook extends Piece {
  getMoves(board) {
    return this.basicMoves(board, [[1, 0], [-1, 0], [0, 1], [0, -1]], true);
  }
}

class Bishop extends Piece {
  getMoves(board) {
    return this.basicMoves(board, [[1, 1], [1, -1], [-1, 1], [-1, -1]], true);
  }
}

class Queen extends Piece {
  getMoves(board) {
    return this.basicMoves(
      board,
      [[1, 0], [-1, 0], [0, 1], [0, -1], [1, 1], [1, -1], [-1, 1], [-1, -1]],
      true
    );
  }
}

class Knight extends Piece {
  getMoves(board) {
    return this.basicMoves(
      board,
      [
        [2, 1],
        [2, -1],
        [-2, 1],
        [-2, -1],
        [1, 2],
        [1, -2],
        [-1, 2],
        [-1, -2]
      ],
      false
    );
  }
}

class King extends Piece {
  getMoves(board) {
    return this.basicMoves(
      board,
      [
        [1, 0],
        [-1, 0],
        [0, 1],
        [0, -1],
        [1, 1],
        [1, -1],
        [-1, 1],
        [-1, -1]
      ],
      false
    );
  }
}

class Game {
  constructor() {
    this.board = this.createEmptyBoard();
    this.currentColor = "white";
    this.selected = null;
    this.validMoves = [];
    this.boardElement = document.getElementById("chessBoard");
    this.turnElement = document.getElementById("chessTurn");
    this.statusElement = document.getElementById("chessStatus");
    this.logElement = document.getElementById("chessLog");
    this.newGameBtn = document.getElementById("chessNewGame");
    this.files = ["a", "b", "c", "d", "e", "f", "g", "h"];
  }
  createEmptyBoard() {
    const board = [];
    for (let r = 0; r < 8; r++) {
      board[r] = [];
      for (let c = 0; c < 8; c++) {
        board[r][c] = null;
      }
    }
    return board;
  }
  setupInitial() {
    this.board = this.createEmptyBoard();
    const back = [Rook, Knight, Bishop, Queen, King, Bishop, Knight, Rook];
    for (let c = 0; c < 8; c++) {
      this.board[6][c] = new Pawn("white", "pawn", 6, c);
      this.board[1][c] = new Pawn("black", "pawn", 1, c);
      this.board[7][c] = new back[c]("white", back[c].name.toLowerCase(), 7, c);
      this.board[0][c] = new back[c]("black", back[c].name.toLowerCase(), 0, c);
    }
    this.currentColor = "white";
    this.selected = null;
    this.validMoves = [];
    this.updateTurnText();
    this.statusElement.textContent = "";
    this.logElement.innerHTML = "";
  }
  coordToNotation(row, col) {
    const file = this.files[col];
    const rank = 8 - row;
    return file + rank;
  }
  render() {
    this.boardElement.innerHTML = "";
    for (let r = 0; r < 8; r++) {
      for (let c = 0; c < 8; c++) {
        const cell = document.createElement("div");
        cell.className = "chess-cell " + ((r + c) % 2 === 0 ? "chess-light" : "chess-dark");
        cell.dataset.row = String(r);
        cell.dataset.col = String(c);
        const piece = this.board[r][c];
        if (piece) {
          cell.textContent = piece.symbol();
        }
        if (this.selected && this.selected.row === r && this.selected.col === c) {
          cell.classList.add("chess-selected");
        }
        const move = this.validMoves.find(m => m.row === r && m.col === c);
        if (move) {
          if (move.capture) cell.classList.add("chess-capture");
          else cell.classList.add("chess-move");
        }
        const coord = document.createElement("div");
        coord.className = "chess-coords";
        if (r === 7) {
          const fileText = document.createElement("span");
          fileText.className = "chess-coords-file";
          fileText.textContent = this.files[c];
          coord.appendChild(fileText);
        }
        if (c === 0) {
          const rankText = document.createElement("span");
          rankText.className = "chess-coords-rank";
          rankText.textContent = String(8 - r);
          coord.appendChild(rankText);
        }
        cell.appendChild(coord);
        this.boardElement.appendChild(cell);
      }
    }
  }
  updateTurnText() {
    this.turnElement.textContent = this.currentColor === "white" ? "белые" : "чёрные";
  }
  handleCellClick(row, col) {
    const piece = this.board[row][col];
    if (this.selected) {
      const move = this.validMoves.find(m => m.row === row && m.col === col);
      if (move) {
        this.movePiece(this.selected.row, this.selected.col, row, col);
        this.selected = null;
        this.validMoves = [];
        this.render();
        return;
      }
      this.selected = null;
      this.validMoves = [];
      this.render();
      if (piece && piece.color === this.currentColor) {
        this.selectPiece(row, col);
      }
      return;
    }
    if (!piece || piece.color !== this.currentColor) return;
    this.selectPiece(row, col);
  }
  selectPiece(row, col) {
    const piece = this.board[row][col];
    if (!piece) return;
    this.selected = piece;
    const rawMoves = piece.getMoves(this.board);
    this.validMoves = rawMoves.map(m => {
      const target = this.board[m.row][m.col];
      return { row: m.row, col: m.col, capture: !!target };
    });
    this.render();
  }
  movePiece(fromRow, fromCol, toRow, toCol) {
    const piece = this.board[fromRow][fromCol];
    const target = this.board[toRow][toCol];
    if (!piece) return;
    const from = this.coordToNotation(fromRow, fromCol);
    const to = this.coordToNotation(toRow, toCol);
    const text =
      (piece.type === "pawn" ? "" : piece.symbol()) +
      from +
      (target ? "x" : "‑") +
      to;
    const p = document.createElement("p");
    p.textContent = (this.currentColor === "white" ? "Белые: " : "Чёрные: ") + text;
    this.logElement.appendChild(p);
    this.logElement.scrollTop = this.logElement.scrollHeight;
    this.board[toRow][toCol] = piece;
    this.board[fromRow][fromCol] = null;
    piece.row = toRow;
    piece.col = toCol;
    piece.hasMoved = true;
    this.currentColor = this.currentColor === "white" ? "black" : "white";
    this.updateTurnText();
    this.statusElement.textContent = "";
  }
  init() {
    this.setupInitial();
    this.render();
    this.boardElement.addEventListener("click", e => {
      const cell = e.target.closest(".chess-cell");
      if (!cell) return;
      const row = Number(cell.dataset.row);
      const col = Number(cell.dataset.col);
      this.handleCellClick(row, col);
    });
    this.newGameBtn.addEventListener("click", () => {
      this.setupInitial();
      this.render();
    });
  }
}

document.addEventListener("DOMContentLoaded", () => {
  const game = new Game();
  game.init();
});


```

