복잡한 게임 로직을 위한 JavaScript 스타일 스크립팅

레벨 3: Script (스크립트)

레벨 3은 선언적으로 표현할 수 없는 복잡한 게임 로직을 구현하기 위한 JavaScript 스타일 스크립팅 언어를 제공합니다.

개요

레벨 3이 유용한 경우:

  • 복잡한 AI 동작
  • 커스텀 이동 유효성 검사
  • 동적 기물 능력
  • 고급 게임 상태 조작
  • 외부 시스템과의 통합

스크립트 블록

스크립팅 코드는 script 블록 안에 작성합니다:

chesslang
script {
  // JavaScript 스타일 코드를 여기에 작성
}

변수와 타입

변수 선언

chesslang
script {
  let x = 5;           // 변경 가능한 변수
  const y = 10;        // 변경 불가능한 상수

  let name = "Rook";   // 문자열
  let active = true;   // 불리언
  let pieces = [];     // 배열
  let config = {};     // 객체
}

타입 추론

타입은 자동으로 추론됩니다:

chesslang
script {
  let count = 0;           // number
  let message = "Hello";   // string
  let valid = true;        // boolean
  let pos = { x: 1, y: 2 }; // object
}

제어 흐름

조건문

chesslang
script {
  if (piece.type == "King") {
    // 킹 관련 로직
  } else if (piece.type == "Queen") {
    // 퀸 관련 로직
  } else {
    // 다른 기물
  }
}

반복문

chesslang
script {
  // For 루프
  for (let i = 0; i < 8; i++) {
    // 각 파일 처리
  }

  // For-of 루프
  for (const piece of board.pieces) {
    // 각 기물 처리
  }

  // While 루프
  while (condition) {
    // 조건이 거짓이 될 때까지 반복
  }
}

함수

함수 선언

chesslang
script {
  function calculateValue(piece) {
    if (piece.type == "Queen") return 9;
    if (piece.type == "Rook") return 5;
    if (piece.type == "Bishop") return 3;
    if (piece.type == "Knight") return 3;
    if (piece.type == "Pawn") return 1;
    return 0;
  }
}

화살표 함수

chesslang
script {
  const isEnemy = (piece, player) => piece.owner != player;

  const sumValues = (pieces) => {
    let total = 0;
    for (const p of pieces) {
      total += calculateValue(p);
    }
    return total;
  };
}

내장 객체

board

게임 보드에 접근합니다:

chesslang
script {
  // 위치의 기물 가져오기
  const piece = board.at({ file: 4, rank: 0 });

  // 모든 기물 가져오기
  const allPieces = board.pieces;

  // 플레이어별 기물 가져오기
  const whitePieces = board.pieces.filter(p => p.owner == "White");

  // 칸이 비어있는지 확인
  const isEmpty = board.isEmpty({ file: 3, rank: 3 });

  // 인접 칸 가져오기
  const neighbors = board.adjacent({ file: 4, rank: 4 });
}

game

게임 상태에 접근합니다:

chesslang
script {
  // 현재 플레이어
  const current = game.currentPlayer;

  // 이동 기록
  const moves = game.moveHistory;
  const lastMove = game.lastMove;

  // 턴 번호
  const turn = game.turnNumber;

  // 체크 상태
  const inCheck = game.isCheck(player);
  const isCheckmate = game.isCheckmate(player);

  // 게임 결과
  const isOver = game.isOver;
  const winner = game.winner;
}

위치 유틸리티

chesslang
script {
  // 칸 표기법 파싱
  const pos = parseSquare("e4");  // { file: 4, rank: 3 }

  // 표기법으로 변환
  const notation = toSquare({ file: 4, rank: 3 });  // "e4"

  // 거리 계산
  const dist = distance(pos1, pos2);

  // 방향으로 칸 가져오기
  const ray = getRay(pos, "N", 3);  // 북쪽으로 3칸

  // 위치 유효성 확인
  const valid = isValidPos({ file: 8, rank: 0 });  // false
}

이벤트 핸들러

글로벌 이벤트 핸들러

chesslang
script {
  game.on("move", function(event) {
    console.log(event.piece.type, "moved from", event.from, "to", event.to);
  });

  game.on("capture", function(event) {
    console.log(event.captured.type, "was captured!");
  });

  game.on("turnEnd", function(event) {
    // 특수 조건 확인
    if (game.turnNumber > 50) {
      // 시간 압박 효과 적용
    }
  });
}

완전한 예제

Progressive Chess

chesslang
game: "Progressive Chess"
extends: "Standard Chess"

script {
  // 턴당 이동 추적
  game.state.movesThisTurn = 0;
  game.state.movesRequired = 1;

  game.on("move", function(event) {
    game.state.movesThisTurn++;

    // 체크 확인 - 턴 즉시 종료
    if (game.isCheck(event.player === "White" ? "Black" : "White")) {
      game.endTurn();
      return;
    }

    // 필요한 이동 완료 확인
    if (game.state.movesThisTurn >= game.state.movesRequired) {
      game.endTurn();
    }
  });

  game.on("turnEnd", function(event) {
    // 다음 플레이어는 이동 하나 더
    game.state.movesRequired++;
    game.state.movesThisTurn = 0;
  });
}

Countdown Chess

chesslang
game: "Countdown Chess"
extends: "Standard Chess"

script {
  game.state.whiteMoves = 50;
  game.state.blackMoves = 50;

  game.on("move", function(event) {
    if (event.player === "White") {
      game.state.whiteMoves--;
      console.log("White moves left:", game.state.whiteMoves);
      if (game.state.whiteMoves <= 0) {
        game.declareWinner("Black", "White ran out of moves");
      }
    } else {
      game.state.blackMoves--;
      console.log("Black moves left:", game.state.blackMoves);
      if (game.state.blackMoves <= 0) {
        game.declareWinner("White", "Black ran out of moves");
      }
    }
    game.endTurn();
  });
}

Material Race

chesslang
game: "Material Race"
extends: "Standard Chess"

script {
  game.state.whiteCaptures = 0;
  game.state.blackCaptures = 0;

  var pieceValues = {
    "Pawn": 1,
    "Knight": 3,
    "Bishop": 3,
    "Rook": 5,
    "Queen": 9
  };

  game.on("capture", function(event) {
    var capturedType = event.captured.type;
    var value = pieceValues[capturedType] || 0;

    if (event.player === "White") {
      game.state.whiteCaptures += value;
      console.log("White captures:", game.state.whiteCaptures);
      if (game.state.whiteCaptures >= 15) {
        game.declareWinner("White", "Captured 15 points");
      }
    } else {
      game.state.blackCaptures += value;
      console.log("Black captures:", game.state.blackCaptures);
      if (game.state.blackCaptures >= 15) {
        game.declareWinner("Black", "Captured 15 points");
      }
    }
  });
}

디버깅

콘솔 출력

chesslang
script {
  console.log("Debug message");
  console.warn("Warning message");
  console.error("Error message");

  // 객체 로그
  console.log("Piece:", piece);
  console.log("Board state:", board.toJSON());
}

모범 사례

  1. 스크립트를 집중적으로 유지: 선언적 로직은 레벨 2, 복잡한 절차는 레벨 3 사용
  2. 함수 문서화: 함수가 하는 일을 설명하는 주석 추가
  3. 오류 처리: null/undefined 값 확인
  4. 부작용 방지: 함수는 예측 가능해야 함
  5. 철저히 테스트: 스크립트는 미묘한 버그를 유발할 수 있음

제한 사항

  • async/await 없음 (게임 로직은 동기적)
  • DOM 접근 없음 (격리된 컨텍스트에서 실행)
  • 외부 import 없음 (내장 유틸리티 사용)
  • 제한된 재귀 깊이 (무한 루프 방지)

다음 단계

  • 모든 키워드는 레퍼런스를 참고하세요
  • 복잡한 변형은 예제를 탐색하세요
  • 이동 레퍼런스는 패턴을 확인하세요
  • 스크립트 API 상세 정보는 스크립트 API를 참고하세요