LineBattle / index.html
kolaslab's picture
Update index.html
ae69bc4 verified
raw
history blame
13.1 kB
<!DOCTYPE html>
<html lang="ko">
<head>
<meta charset="UTF-8">
<title>๋ผ์ธ๋ฐฐํ‹€: AI ์‹ฑ๊ธ€ํ”Œ๋ ˆ์ด ์˜ˆ์‹œ</title>
<style>
/* ๊ฐ„๋‹จํ•œ ์Šคํƒ€์ผ */
body {
margin: 20px;
font-family: sans-serif;
}
h1 {
margin-bottom: 10px;
}
#infoBar {
margin-bottom: 10px;
}
#gameBoard {
display: grid;
grid-template-columns: repeat(10, 40px);
grid-template-rows: repeat(6, 40px);
gap: 2px;
margin-bottom: 10px;
}
.cell {
width: 40px;
height: 40px;
border: 1px solid #666;
display: flex;
align-items: center;
justify-content: center;
cursor: pointer;
background-color: #ccc;
}
.red {
background-color: #ffaaaa;
}
.blue {
background-color: #aaaaff;
}
#endTurnBtn {
padding: 6px 12px;
font-size: 14px;
cursor: pointer;
margin-right: 10px;
}
</style>
</head>
<body>
<h1>๋ผ์ธ๋ฐฐํ‹€: AI ์‹ฑ๊ธ€ํ”Œ๋ ˆ์ด ์˜ˆ์‹œ</h1>
<div id="infoBar">
<span id="turnInfo"></span> |
<span id="selectedInfo"></span>
</div>
<div id="gameBoard"></div>
<button id="endTurnBtn">ํ„ด ์ข…๋ฃŒ</button>
<div id="message"></div>
<script>
/********************************************************
* 1. ์œ ๋‹›/๊ฒŒ์ž„ ํŒŒ๋ผ๋ฏธํ„ฐ ์ •์˜
********************************************************/
const UNIT_TYPES = {
INFANTRY: {
name: "Infantry",
hp: 25,
attack: 3,
defense: 3,
move: 2,
range: 1,
advantage: "ARCHER", // ๋ณด๋ณ‘ > ๊ถ์ˆ˜
},
ARCHER: {
name: "Archer",
hp: 20,
attack: 2,
defense: 2,
move: 2,
range: 3,
advantage: "CAVALRY", // ๊ถ์ˆ˜ > ๊ธฐ๋ณ‘
},
CAVALRY: {
name: "Cavalry",
hp: 22,
attack: 4,
defense: 2,
move: 3,
range: 1,
advantage: "INFANTRY", // ๊ธฐ๋ณ‘ > ๋ณด๋ณ‘
}
};
// ๊ฐ„๋‹จํ•œ ์ƒ์„ฑ ๋ณด์ •(์˜ˆ: 1.5๋ฐฐ)
const ADVANTAGE_MULTIPLIER = 1.5;
// ๋งต ํฌ๊ธฐ
const WIDTH = 10;
const HEIGHT = 6;
// ํŒ€ ์ •์˜
const RED = "RED";
const BLUE = "BLUE";
// ๊ฒŒ์ž„ ์ƒํƒœ
let units = []; // ์ „์ฒด ์œ ๋‹› ๊ฐ์ฒด๋ฅผ ๋ณด๊ด€
let currentTeam = RED; // ํ˜„์žฌ ํ„ด์˜ ํŒ€
let selectedUnit = null; // ์œ ์ €๊ฐ€ ์„ ํƒํ•œ ์œ ๋‹›(RED ํ„ด ์ค‘)
/********************************************************
* 2. ์œ ๋‹› ํด๋ž˜์Šค
********************************************************/
class Unit {
constructor(typeKey, team, x, y) {
const data = UNIT_TYPES[typeKey];
this.type = typeKey; // "INFANTRY", "ARCHER", "CAVALRY"
this.team = team; // "RED" ๋˜๋Š” "BLUE"
this.hp = data.hp;
this.attack = data.attack;
this.defense = data.defense;
this.move = data.move;
this.range = data.range;
this.advantage = data.advantage;
this.x = x;
this.y = y;
this.hasActed = false; // ์ด ํ„ด ์ค‘ ์ด๋™/๊ณต๊ฒฉ ์—ฌ๋ถ€๋ฅผ ๋‹จ์ˆœ ์ฒดํฌ(ํ™•์žฅ ๊ฐ€๋Šฅ)
}
get isAlive() {
return this.hp > 0;
}
get displayName() {
// ์˜ˆ: 'I25' (Infantry, HP 25)
return this.type[0] + this.hp;
}
}
/********************************************************
* 3. ์ดˆ๊ธฐํ™”: ์œ ๋‹› ๋ฐฐ์น˜
********************************************************/
function initUnits() {
units = [];
// RED - ๋ณด๋ณ‘(6), ๊ถ์ˆ˜(3), ๊ธฐ๋ณ‘(3) / ์˜ˆ์‹œ๋กœ ์ขŒ์ธก์— ๋ฐฐ์น˜
for (let i = 0; i < 6; i++) {
units.push(new Unit("INFANTRY", RED, 0, i % HEIGHT));
}
for (let i = 0; i < 3; i++) {
units.push(new Unit("ARCHER", RED, 1, i));
}
for (let i = 0; i < 3; i++) {
units.push(new Unit("CAVALRY", RED, 2, i + 3));
}
// BLUE - ๋ณด๋ณ‘(6), ๊ถ์ˆ˜(3), ๊ธฐ๋ณ‘(3) / ์˜ˆ์‹œ๋กœ ์šฐ์ธก์— ๋ฐฐ์น˜
for (let i = 0; i < 6; i++) {
units.push(new Unit("INFANTRY", BLUE, WIDTH - 1, i % HEIGHT));
}
for (let i = 0; i < 3; i++) {
units.push(new Unit("ARCHER", BLUE, WIDTH - 2, i));
}
for (let i = 0; i < 3; i++) {
units.push(new Unit("CAVALRY", BLUE, WIDTH - 3, i + 3));
}
}
/********************************************************
* 4. ํ™”๋ฉด ๋ Œ๋”๋ง
********************************************************/
function renderBoard() {
const board = document.getElementById("gameBoard");
board.innerHTML = "";
// ๋งต์„ HEIGHT x WIDTH ์ˆœํšŒํ•˜๋ฉฐ ์…€ ์ƒ์„ฑ
for (let y = 0; y < HEIGHT; y++) {
for (let x = 0; x < WIDTH; x++) {
const cellDiv = document.createElement("div");
cellDiv.classList.add("cell");
cellDiv.dataset.x = x;
cellDiv.dataset.y = y;
// ์ด ์ขŒํ‘œ์— ์œ ๋‹›์ด ์žˆ๋Š”์ง€ ํ™•์ธ
const unitHere = units.find(u => u.isAlive && u.x === x && u.y === y);
if (unitHere) {
if (unitHere.team === RED) cellDiv.classList.add("red");
else cellDiv.classList.add("blue");
cellDiv.textContent = unitHere.displayName;
}
// ํด๋ฆญ ์ด๋ฒคํŠธ
cellDiv.addEventListener("click", () => onCellClick(x, y));
board.appendChild(cellDiv);
}
}
// ์ƒ๋‹จ ์ •๋ณด
document.getElementById("turnInfo").textContent = `ํ˜„์žฌ ํ„ด: ${currentTeam}`;
if (selectedUnit) {
document.getElementById("selectedInfo").textContent =
`์„ ํƒ ์œ ๋‹›: ${selectedUnit.type} (HP: ${selectedUnit.hp})`;
} else {
document.getElementById("selectedInfo").textContent = `์„ ํƒ ์œ ๋‹›: ์—†์Œ`;
}
}
/********************************************************
* 5. ์œ ์ € ์ž…๋ ฅ ์ฒ˜๋ฆฌ
********************************************************/
function onCellClick(x, y) {
if (currentTeam !== RED) {
// ํ”Œ๋ ˆ์ด์–ด ํ„ด์ด ์•„๋‹ ๋•Œ๋Š” ํด๋ฆญ ๋ฌด์‹œ
return;
}
// ํด๋ฆญ ์ง€์ ์— ์œ ๋‹›์ด ์žˆ๋Š”์ง€ ํ™•์ธ
const clickedUnit = units.find(u => u.isAlive && u.x === x && u.y === y);
if (!selectedUnit) {
// ์•„์ง ์œ ๋‹›์„ ์„ ํƒํ•˜์ง€ ์•Š์€ ์ƒํƒœ
if (clickedUnit && clickedUnit.team === RED) {
// ์•„๊ตฐ ์œ ๋‹›์ด๋ฉด ์„ ํƒ
if (!clickedUnit.hasActed) {
selectedUnit = clickedUnit;
}
}
} else {
// ์ด๋ฏธ ์œ ๋‹›์„ ์„ ํƒํ•œ ์ƒํƒœ
if (clickedUnit) {
// ๊ณต๊ฒฉ ์‹œ๋„ ์—ฌ๋ถ€ ํ™•์ธ
if (clickedUnit.team !== RED) {
// ์  ์œ ๋‹›์ด๋ฉด ๊ณต๊ฒฉ ์‹œ๋„
const dist = getDistance(selectedUnit, clickedUnit);
if (dist <= selectedUnit.range) {
// ๊ณต๊ฒฉ
attack(selectedUnit, clickedUnit);
selectedUnit.hasActed = true;
selectedUnit = null;
}
} else {
// ๊ฐ™์€ ํŒ€ ์œ ๋‹› ํด๋ฆญ => ์„ ํƒ ์œ ๋‹› ๋ณ€๊ฒฝ(๋‹จ, ์ด๋ฏธ ํ–‰๋™ ๋๋‚œ ์œ ๋‹›์€ ์„ ํƒ ๋ถˆ๊ฐ€)
if (!clickedUnit.hasActed) {
selectedUnit = clickedUnit;
}
}
} else {
// ๋นˆ ์นธ ํด๋ฆญ -> ์ด๋™ ์‹œ๋„
const dist = Math.abs(selectedUnit.x - x) + Math.abs(selectedUnit.y - y);
// (๊ฐ„๋‹จํžˆ ๋งจํ•ดํŠผ ๊ฑฐ๋ฆฌ๋กœ๋งŒ ๊ณ„์‚ฐ. ๋Œ€๊ฐ ์ด๋™/์žฅ์• ๋ฌผ์€ ๊ณ ๋ ค ์•ˆ ํ•จ)
if (dist <= selectedUnit.move) {
selectedUnit.x = x;
selectedUnit.y = y;
// ์ด๋™๋งŒ ํ•˜๊ณ  ์‹ถ์œผ๋ฉด ์—ฌ๊ธฐ์„œ hasActed๋ฅผ true๋กœ ํ•˜๊ฑฐ๋‚˜,
// ์ด๋™ ํ›„ ๊ณต๊ฒฉ์„ ํ—ˆ์šฉํ• ์ง€ ์—ฌ๋ถ€๋Š” ๊ทœ์น™์— ๋”ฐ๋ผ ๊ฒฐ์ •
// ์—ฌ๊ธฐ์„  ์ด๋™ํ•ด๋„ ๊ณต๊ฒฉ ๊ฐ€๋Šฅํ•˜๋„๋ก hasActed = false ์œ ์ง€
}
}
}
renderBoard();
}
// ๊ณต๊ฒฉ ํ•จ์ˆ˜
function attack(attacker, defender) {
if (!attacker || !defender) return;
const baseDamage = Math.max(0, attacker.attack - defender.defense);
let finalDamage = baseDamage;
// ์ƒ์„ฑ ์ฒดํฌ
if (attacker.advantage === defender.type) {
finalDamage = Math.floor(finalDamage * ADVANTAGE_MULTIPLIER);
}
defender.hp -= finalDamage;
// ๋ฉ”์„ธ์ง€ ์ถœ๋ ฅ
const msg =
`[${attacker.team} ${attacker.type}]๊ฐ€ [${defender.team} ${defender.type}]๋ฅผ ๊ณต๊ฒฉ! (๋ฐ๋ฏธ์ง€ ${finalDamage})`;
showMessage(msg);
// ์‚ฌ๋ง ์ฒดํฌ
if (defender.hp <= 0) {
defender.hp = 0;
showMessage(`โ†’ [${defender.team} ${defender.type}] ๊ฒฉํŒŒ!`);
}
}
// ๋ฉ”์„ธ์ง€ ํ‘œ์‹œ(๋‹จ์ˆœํžˆ ๋ˆ„์  ์ถœ๋ ฅ)
function showMessage(msg) {
const messageDiv = document.getElementById("message");
messageDiv.innerHTML += msg + "<br>";
messageDiv.scrollTop = messageDiv.scrollHeight;
}
/********************************************************
* 6. ํ„ด ์ข…๋ฃŒ & AI ๋™์ž‘
********************************************************/
function endTurn() {
// ํ”Œ๋ ˆ์ด์–ด๊ฐ€ ํ„ด ์ข…๋ฃŒ ํด๋ฆญ
if (currentTeam === RED) {
// ๋ชจ๋“  ์œ ๋‹›์˜ hasActed ์ดˆ๊ธฐํ™”
units.forEach(u => {
if (u.team === RED) {
u.hasActed = false;
}
});
// ํ„ด ๊ต์ฒด
currentTeam = BLUE;
selectedUnit = null;
renderBoard();
// AI ์ˆ˜ํ–‰
setTimeout(() => performAiTurn(), 500);
}
}
// AI ๊ฐ„๋‹จ ๋กœ์ง
function performAiTurn() {
// 1. ํ–‰๋™ํ•  ์ˆ˜ ์žˆ๋Š” BLUE ์œ ๋‹› ๋ชฉ๋ก
const blueUnits = units.filter(u => u.team === BLUE && u.isAlive);
// ๊ฐ„๋‹จํ•œ ์ˆœ์ฐจ ์ฒ˜๋ฆฌ: ๊ฐ ์œ ๋‹›๋ณ„๋กœ ์ œ์ผ ๊ฐ€๊นŒ์šด ์ (RED)์„ ํƒ์ƒ‰โ†’์ด๋™โ†’๊ณต๊ฒฉ
for (const aiUnit of blueUnits) {
// ๊ฐ€์žฅ ๊ฐ€๊นŒ์šด RED ์œ ๋‹› ์ฐพ๊ธฐ
const enemies = units.filter(u => u.team === RED && u.isAlive);
if (enemies.length === 0) break; // ์ ์ด ์—†์œผ๋ฉด ์ข…๋ฃŒ
let closestEnemy = null;
let minDist = 9999;
for (const e of enemies) {
const d = getDistance(aiUnit, e);
if (d < minDist) {
minDist = d;
closestEnemy = e;
}
}
if (!closestEnemy) continue;
// ๊ณต๊ฒฉ ๋ฒ”์œ„ ์•ˆ์— ์žˆ์œผ๋ฉด ๊ณต๊ฒฉ
if (minDist <= aiUnit.range) {
attack(aiUnit, closestEnemy);
} else {
// ๋ฒ”์œ„ ๋ฐ–์ด๋ฉด ์ด๋™(๊ฐ€์žฅ ๊ฐ„๋‹จํžˆ, ๊ฐ€๊นŒ์›Œ์ง€๋„๋ก x,y๋ฅผ 1์นธ ์ด๋™)
const moveStep = getSimpleMoveToward(aiUnit, closestEnemy);
if (moveStep) {
aiUnit.x = clamp(aiUnit.x + moveStep.dx, 0, WIDTH - 1);
aiUnit.y = clamp(aiUnit.y + moveStep.dy, 0, HEIGHT - 1);
// ์ด๋™ ํ›„, ์‚ฌ๊ฑฐ๋ฆฌ ์•ˆ์ด๋ฉด ๋‹ค์‹œ ๊ณต๊ฒฉ
const distAfterMove = getDistance(aiUnit, closestEnemy);
if (distAfterMove <= aiUnit.range) {
attack(aiUnit, closestEnemy);
}
}
}
}
// AI ํ„ด ์ข…๋ฃŒ
currentTeam = RED;
// AI ์œ ๋‹› hasActed ์ดˆ๊ธฐํ™”
units.forEach(u => {
if (u.team === BLUE) {
u.hasActed = false;
}
});
renderBoard();
checkWinCondition();
}
/********************************************************
* 7. ๋ณด์กฐ ํ•จ์ˆ˜๋“ค
********************************************************/
// ๋‘ ์œ ๋‹› ๊ฐ„ ๊ฑฐ๋ฆฌ(๊ฐ„๋‹จํžˆ ๋งจํ•ดํŠผ ๊ฑฐ๋ฆฌ)
function getDistance(u1, u2) {
return Math.abs(u1.x - u2.x) + Math.abs(u1.y - u2.y);
}
// AI ์ด๋™์šฉ: ๋ชฉํ‘œ ์  ์œ ๋‹›์— ์กฐ๊ธˆ ๋” ๋‹ค๊ฐ€๊ฐ€๋Š” ๋ฐฉํ–ฅ ๊ณ„์‚ฐ
function getSimpleMoveToward(aiUnit, target) {
// ์ด๋™๋ ฅ๋งŒํผ ์„ธ๋ฐ€ํžˆ ๊ณ„์‚ฐํ•˜๊ธฐ๋ณด๋‹ค๋Š”, 1์นธ๋งŒ ์ „์ง„(๋˜๋Š” 2์นธ) ํ•˜๋Š” ์‹
const dx = target.x - aiUnit.x;
const dy = target.y - aiUnit.y;
let stepX = 0;
let stepY = 0;
if (Math.abs(dx) > Math.abs(dy)) {
stepX = dx > 0 ? 1 : -1;
} else {
stepY = dy > 0 ? 1 : -1;
}
// aiUnit.move ๋งŒํผ ์ด๋™ํ•  ์ˆ˜๋„ ์žˆ์ง€๋งŒ, ์—ฌ๊ธฐ์„  ๋‹จ์ˆœํ™”ํ•ด์„œ 1์นธ๋งŒ ์ด๋™
return { dx: stepX, dy: stepY };
}
// ๋ฒ”์œ„ ์ œํ•œ ํ•จ์ˆ˜
function clamp(value, min, max) {
return Math.max(min, Math.min(max, value));
}
// ์Šน๋ฆฌ ์กฐ๊ฑด ์ฒดํฌ
function checkWinCondition() {
const redAlive = units.some(u => u.team === RED && u.isAlive);
const blueAlive = units.some(u => u.team === BLUE && u.isAlive);
if (!redAlive) {
showMessage("BLUE ์Šน๋ฆฌ!");
} else if (!blueAlive) {
showMessage("RED ์Šน๋ฆฌ!");
}
}
/********************************************************
* 8. ์ดˆ๊ธฐ ์‹คํ–‰
********************************************************/
// ์ดˆ๊ธฐ ์„ธํŒ…
initUnits();
renderBoard();
// ํ„ด ์ข…๋ฃŒ ๋ฒ„ํŠผ
document.getElementById("endTurnBtn").addEventListener("click", () => {
endTurn();
checkWinCondition();
});
</script>
</body>
</html>