diff --git a/AGENTS.md b/AGENTS.md new file mode 100644 index 0000000..cef252f --- /dev/null +++ b/AGENTS.md @@ -0,0 +1,18 @@ +# Repository Guidelines + +## Project Structure & Module Organization +`index.html` is the single-page shell; it links the modern styling in `battleship.css` and boots `battleship.js` as an ES module. Gameplay logic lives under `src/`: `game.js` orchestrates turns and win states, `board.js` tracks ship placement and attacks, `ship.js` models individual vessels, `ai-controller.js` handles smart CPU targeting, and `ui-controller.js` updates the DOM. Shared constants (ship specs, orientations, board size) are defined in `src/constants.js` so both players stay in sync. `battleship.js` wires the controllers together, exposes the global bridge required by the existing `onclick` handlers, and inserts the current year into the footer. Static assets (splash/explosion SVGs, icons, screenshots) live in `images/`; prefer `.webp` for screenshots and keep SVGs optimized (`svgo` or similar) when adding new art. + +## Build, Test, and Development Commands +- `npx http-server . --port 3000` serves the project with correct relative paths and caching headers; use it during active development. +- `python3 -m http.server 3000` provides a zero-dependency fallback if Node.js is unavailable. +- `open index.html` (or your OS equivalent) is fine for quick visual checks, but an HTTP server avoids asset/CORS issues and mirrors production loading better. + +## Coding Style & Naming Conventions +Follow the ES module pattern already in place: default to `const`/`let`, keep classes in their own files, and export the minimal surface each module needs. JavaScript uses two-space indentation and descriptive method names (`placePlayerShip`, `handleAttackResult`). CSS also uses two spaces and kebab-case selectors; leverage the variables at the top of `battleship.css`—especially `--cell-size` for board square sizing—rather than hard-coding pixels. DOM IDs remain camelCase (`remove-on-start` is the lone kebab-case ID for legacy reasons); keep new classes prefixed with the component they style (`.player-board`, `.ship-select`) to make overrides predictable. + +## Testing Guidelines +There is no automated suite yet, so rely on manual QA. Serve the site, then walk through ship placement, game start, repeated attack loops, and both win and loss flows. Confirm the hover preview outlines the full ship footprint (blue when valid, red when out of bounds or overlapping) before you click, the CPU AI switches from random attacks to focused hunts after a hit, and the retry button reloads cleanly. Validate responsive breakpoints at 1280px, 1024px, 768px, and 375px widths—the UI should keep both boards visible side by side on desktop and wrap sensibly on smaller screens. Capture GIFs or annotated screenshots for regressions and attach them to your PR. + +## Commit & Pull Request Guidelines +History favors concise, lower-case subjects (`add retry button logic`). Keep messages imperative, under ~72 characters, and group related CSS/JS changes in a single commit where practical. PRs should include: (1) a short summary, (2) manual test notes listing the commands above, (3) before/after visuals for UI tweaks, and (4) linked issues or TODO references. Request reviewers aligned with the change (UI polish, gameplay logic, asset updates) to speed feedback. For larger refactors, note any follow-up tasks (e.g., migrating from inline `onclick` attributes) so the next contributor can pick them up easily. diff --git a/battleship.css b/battleship.css index 7e6256e..5109288 100644 --- a/battleship.css +++ b/battleship.css @@ -1,765 +1,645 @@ -body { - color: rgb(112, 134, 190); - background-color: rgb(61, 63, 70); - text-shadow: 2px 1px 1px rgb(0, 0, 0); +:root { + --surface-dark: rgba(6, 15, 36, 0.85); + --surface-card: rgba(13, 26, 53, 0.9); + --surface-panel: rgba(20, 33, 62, 0.8); + --accent-primary: #62f6ff; + --accent-secondary: #ff8ba7; + --accent-tertiary: #8c5bff; + --text-strong: #f5f7ff; + --text-body: #c8cff7; + --text-muted: #7b84b8; + --grid-line: rgba(98, 246, 255, 0.18); + --cell-size: 45px; + --border-radius: 18px; + font-family: "Space Grotesk", "Segoe UI", sans-serif; +} + +*, +*::before, +*::after { + box-sizing: border-box; } -h1 { +body { margin: 0; -} - -.container { + min-height: 100vh; display: flex; - flex-direction: column; - justify-content: space-between; - gap: 10px; - min-height: 900px; - width: 100%; -} - -table { - margin: 0 auto; justify-content: center; - text-align: center; align-items: center; - align-content: center; - align-self: center; + padding: 16px 0; + overflow-x: hidden; + background: radial-gradient( + 125% 125% at 50% 0%, + #283773 0%, + #0c1226 55%, + #05070f 100% + ); + color: var(--text-body); } -header { - text-align: center; +::selection { + background: rgba(98, 246, 255, 0.3); + color: var(--text-strong); } -.message { - text-align: center; +a { + color: var(--accent-primary); + text-decoration: none; } -.game-boards { - justify-content: center; - align-items: center; - gap: 50px; +a:hover { + text-decoration: underline; } -.selection { +.container { + width: min(1080px, 94vw); display: flex; flex-direction: column; - text-align: center; - gap: 5px; + gap: 20px; + padding: 24px 0 28px; } -.ship-selection1 { - display: flex; - gap: 4px; -} - -.ship-selection2 { - display: flex; - gap: 4px; +#remove-on-start { + height: 100%; } -.vertical-selection { - margin: auto; - width: 30%; - height: 25px; - color: white; - text-shadow: 2px 1px 1px rgb(0, 0, 0); - font-weight: bold; - border: 3px solid black; - border-radius: 10px; - background-color: grey; - box-shadow: 2px 2px 2px 2px black +header { + text-align: center; + padding: 26px 24px; + border-radius: var(--border-radius); + background: linear-gradient( + 135deg, + rgba(98, 246, 255, 0.16), + rgba(140, 91, 255, 0.12) + ), + var(--surface-panel); + backdrop-filter: blur(12px); + box-shadow: 0 32px 60px rgba(0, 0, 0, 0.35); +} + +header h1 { + margin: 0; + font-size: clamp(2.2rem, 3vw, 2.7rem); + font-weight: 700; + letter-spacing: 0.08em; + text-transform: uppercase; + color: var(--text-strong); } -.vertical-selection:hover { - background-color: rgb(6, 94, 121); +header { + font-size: 1rem; + letter-spacing: 0.04em; } -.vertical-selection:active { - background-color: rgb(19, 17, 17); +.container-main { + display: grid; + gap: 18px; + grid-template-columns: 320px 1fr; + align-items: start; + justify-content: center; } -.horizontal-selection { - margin: auto; - width: 30%; - height: 25px; - color: white; - text-shadow: 2px 1px 1px rgb(2, 2, 2); - font-weight: bold; - border: 3px solid black; - border-radius: 10px; - background-color: grey; - box-shadow: 2px 2px 2px 2px black +.container-main.battle-mode { + grid-template-columns: 1fr; + justify-items: center; } -.horizontal-selection:hover { - background-color: rgb(6, 94, 121); +.container-main.battle-mode .message { + max-width: 560px; + justify-self: center; } -.horizontal-selection:active { - background-color: rgb(19, 17, 17); +.container-main.battle-mode .game-boards { + margin: 0 auto; + justify-content: center; } -.battleship { - margin: auto; - width: 30%; - height: 25px; - color: white; - text-shadow: 2px 1px 1px rgb(4, 4, 4); - font-weight: bold; - border: 3px solid black; - border-radius: 10px; - background-color: grey; - box-shadow: 2px 2px 2px 2px black -} - -.destroyer { - margin: auto; - width: 30%; - height: 25px; - color: white; - text-shadow: 2px 1px 1px rgb(4, 4, 4); - font-weight: bold; - border: 3px solid black; - border-radius: 10px; - background-color: grey; - box-shadow: 2px 2px 2px 2px black +.message { + min-height: 40px; + padding: 12px 16px; + border-radius: var(--border-radius); + background: var(--surface-card); + box-shadow: 0 22px 45px rgba(0, 0, 0, 0.25); + display: flex; + align-items: center; + justify-content: center; + font-size: 0.98rem; + letter-spacing: 0.02em; } -.destroyer:hover { - background-color: rgb(6, 94, 121); +.message { + grid-column: 1 / -1; } -.destroyer:active { - background-color: rgb(19, 17, 17); +.selection { + display: grid; + gap: 12px; + padding: 18px; + border-radius: var(--border-radius); + background: linear-gradient( + 155deg, + rgba(255, 139, 167, 0.22), + rgba(98, 246, 255, 0.08) + ), + var(--surface-card); + backdrop-filter: blur(10px); + box-shadow: 0 24px 45px rgba(0, 0, 0, 0.3); +} + +.selection strong { + color: var(--text-strong); + font-weight: 600; + letter-spacing: 0.04em; +} + +.selection > div:first-child { + text-transform: uppercase; + font-size: 0.95rem; + color: var(--text-muted); + letter-spacing: 0.16em; +} + +.ship-selection1, +.ship-selection2 { + display: grid; + grid-template-columns: repeat(auto-fit, minmax(150px, 1fr)); + gap: 10px; } -.frigate { - margin: auto; - width: 30%; - height: 25px; - color: white; - text-shadow: 2px 1px 1px rgb(4, 4, 4); - font-weight: bold; - border: 3px solid black; - border-radius: 10px; - background-color: grey; - box-shadow: 2px 2px 2px 2px black +.vertical-selection, +.horizontal-selection, +.battleship, +.destroyer, +.frigate, +.patrol-coastal, +.start-game { + cursor: pointer; + padding: 14px 16px; + border-radius: 14px; + border: 1px solid rgba(98, 246, 255, 0.35); + background: rgba(13, 26, 53, 0.6); + color: var(--text-strong); + font-weight: 600; + text-transform: uppercase; + letter-spacing: 0.08em; + transition: transform 0.18s ease, box-shadow 0.18s ease, + border-color 0.18s ease, background 0.18s ease; + text-shadow: 0 0 14px rgba(98, 246, 255, 0.35); +} + +.vertical-selection:hover, +.horizontal-selection:hover, +.battleship:hover, +.destroyer:hover, +.frigate:hover, +.patrol-coastal:hover, +.start-game:hover { + transform: translateY(-2px) scale(1.015); + box-shadow: 0 12px 28px rgba(98, 246, 255, 0.25); + border-color: rgba(98, 246, 255, 0.6); } -.frigate:hover { - background-color: rgb(6, 94, 121); +.vertical-selection:active, +.horizontal-selection:active, +.battleship:active, +.destroyer:active, +.frigate:active, +.patrol-coastal:active, +.start-game:active { + transform: translateY(0); } -.frigate:active { - background-color: rgb(19, 17, 17); +.start-game { + margin-top: 10px; + background: linear-gradient( + 135deg, + rgba(98, 246, 255, 0.3), + rgba(140, 91, 255, 0.25) + ), + rgba(12, 24, 49, 0.75); + border-color: rgba(255, 255, 255, 0.35); + box-shadow: 0 20px 40px rgba(140, 91, 255, 0.35); } -.patrol-coastal { - margin: auto; - width: 30%; - height: 25px; - color: white; - text-shadow: 2px 1px 1px rgb(4, 4, 4); - font-weight: bold; - border: 3px solid black; - border-radius: 10px; - background-color: grey; - box-shadow: 2px 2px 2px 2px black +.start-game:hover { + box-shadow: 0 28px 48px rgba(140, 91, 255, 0.4); } -.patrol-coastal:hover { - background-color: rgb(6, 94, 121); +.game-boards { + display: flex; + gap: 18px; + justify-content: center; + align-items: flex-start; + flex-wrap: nowrap; + position: relative; } -.patrol-coastal:active { - background-color: rgb(19, 17, 17); +.player-board, +.computer-board { + flex: 0 0 auto; + padding: 14px 14px 16px; + border-radius: var(--border-radius); + background: linear-gradient( + 155deg, + rgba(98, 246, 255, 0.15), + rgba(13, 26, 53, 0.8) + ), + var(--surface-dark); + backdrop-filter: blur(8px); + box-shadow: 0 28px 48px rgba(0, 0, 0, 0.45); + position: relative; + overflow: hidden; +} + +.player-board::before, +.computer-board::before { + content: ""; + position: absolute; + inset: -40% 40% 35% -35%; + background: radial-gradient( + circle at center, + rgba(98, 246, 255, 0.32), + transparent 60% + ); + opacity: 0.3; + pointer-events: none; } -.start-game { - margin: auto; - margin-top: 20px; - margin-bottom: 20px; - padding-top: 10px; +.label { + display: inline-flex; align-items: center; justify-content: center; - width: 30%; - height: 25px; - color: white; - text-shadow: 2px 1px 1px rgb(4, 4, 4); - font-weight: bold; - border: 3px solid black; - border-radius: 10px; - background-color: rgb(218, 12, 12); - box-shadow: 2px 2px 2px 2px black -} - -.game-boards { - display: flex; - text-align: center; + padding: 6px 14px; + margin: 0 auto 12px; + border-radius: 32px; + background: rgba(98, 246, 255, 0.16); + color: var(--accent-primary); + text-transform: uppercase; + font-size: 0.8rem; + letter-spacing: 0.18em; + position: relative; + z-index: 1; } -.square { - text-align: center; - background-color: black; - border: 0px solid rgb(103, 1, 1); - padding: 0px; - border-radius: 3px; +table { + margin: 0 auto; + width: calc(var(--cell-size) * 10 + 9 * 5px); + height: calc(var(--cell-size) * 10 + 9 * 5px); + border-collapse: separate; + border-spacing: 5px; + position: relative; + z-index: 1; } td { - padding: 0px; - width: 50px; - height: 50px; + padding: 0; } -.label { - text-align: center; - text-shadow: 2px 2px 2px black; - width: 5px; - padding: 0px; - margin: 0px; - width: 0px; - height: 0px; +.square { + width: var(--cell-size); + height: var(--cell-size); + border-radius: 16px; + background: linear-gradient(145deg, rgba(18, 32, 61, 0.95), rgba(9, 14, 27, 0.9)); + border: 1px solid rgba(98, 246, 255, 0.12); + box-shadow: inset 0 0 0 1px rgba(255, 255, 255, 0.02), + 0 12px 28px rgba(0, 0, 0, 0.4); + position: relative; + overflow: hidden; + transition: transform 0.15s ease, box-shadow 0.15s ease, + border-color 0.15s ease; } -.player-board { - border: 2px solid rgb(0, 0, 0); - color: rgb(151, 182, 219); - font-weight: bold; - font-size: 15px; - background-color: rgb(67, 96, 60); - border-radius: 5px; - box-shadow: 4px 4px 4px 4px black; +.square:hover { + transform: translateY(-2px); + box-shadow: 0 18px 32px rgba(0, 0, 0, 0.45); + border-color: rgba(98, 246, 255, 0.3); } -.computer-board { - border: 2px solid rgb(0, 0, 0); - color: rgb(151, 182, 219); - font-weight: bold; - font-size: 15px; - background-color: rgb(84, 45, 45); - border-radius: 5px; - box-shadow: 4px 4px 4px 4px black +.square span { + display: none; } -footer { - display: flex; - justify-self: space-between; - justify-content: space-evenly; +.square.preview-active::before { + content: ""; + position: absolute; + inset: 5px; + border-radius: 12px; + pointer-events: none; + transition: all 0.15s ease; + z-index: 3; } -.copyright { - display: flex; +.square.preview-valid::before { + background: rgba(98, 246, 255, 0.2); + border: 1px dashed rgba(98, 246, 255, 0.6); + box-shadow: 0 0 12px rgba(98, 246, 255, 0.35); } -.info-link { - display: flex; +.square.preview-invalid::before { + background: rgba(255, 120, 120, 0.18); + border: 1px dashed rgba(255, 120, 120, 0.72); + box-shadow: 0 0 12px rgba(255, 120, 120, 0.3); } -.ship { - background-color: white; - width: 50px; - height: 50px; +.ship-select, +.ship-hit, +.ship-miss { + width: 100%; + height: 100%; + position: relative; + border-radius: 14px; + display: block; +} + +.player-board .ship-select { + background: linear-gradient( + 145deg, + rgba(255, 139, 167, 0.4), + rgba(140, 91, 255, 0.35) + ), + rgba(19, 30, 59, 0.9); + border: 1px solid rgba(255, 139, 167, 0.4); + box-shadow: 0 0 18px rgba(140, 91, 255, 0.35); +} + +.player-board .ship-select::before, +.player-board .ship-select::after { + content: ""; + position: absolute; + inset: 12% 18%; + border-radius: 10px; + background: linear-gradient( + 160deg, + rgba(255, 255, 255, 0.25), + rgba(255, 139, 167, 0.25) + ); + box-shadow: inset 0 0 12px rgba(0, 0, 0, 0.35); +} + +.player-board .ship-select::after { + inset: 34% 32%; + border-radius: 8px; + background: linear-gradient( + 160deg, + rgba(255, 139, 167, 0.55), + rgba(255, 255, 255, 0.45) + ); + opacity: 0.65; +} + +.computer-board .ship-select { + background: linear-gradient( + 145deg, + rgba(98, 246, 255, 0.45), + rgba(5, 120, 176, 0.5) + ), + rgba(9, 29, 56, 0.85); + border: 1px solid rgba(98, 246, 255, 0.4); + box-shadow: 0 0 18px rgba(98, 246, 255, 0.35); +} + +.computer-board .ship-select::before, +.computer-board .ship-select::after { + content: ""; + position: absolute; + inset: 14% 20%; + border-radius: 10px; + background: linear-gradient( + 155deg, + rgba(98, 246, 255, 0.45), + rgba(16, 162, 222, 0.6) + ); + box-shadow: inset 0 0 12px rgba(0, 0, 0, 0.35); } -.ship-select { - background-color: rgba(248, 248, 248, 0); - width: 50px; - height: 50px; - border-radius: 15px; - content: url(./images/pin.svg); +.computer-board .ship-select::after { + inset: 36% 34%; + border-radius: 8px; + background: linear-gradient( + 155deg, + rgba(255, 255, 255, 0.45), + rgba(98, 246, 255, 0.6) + ); + opacity: 0.6; } .ship-hit { - width: 50px; - height: 50px; - background-color: rgba(255, 0, 0, 0.268); - content: url(./images/explosion.svg); - border-radius: 15px; - + background: radial-gradient( + circle at center, + rgba(255, 112, 112, 0.6), + rgba(120, 18, 18, 0.65) 55%, + transparent 70% + ), + rgba(37, 15, 25, 0.85); + border: 1px solid rgba(255, 104, 147, 0.4); + box-shadow: 0 0 22px rgba(255, 112, 112, 0.45); + animation: blast 0.4s ease-out; +} + +.ship-hit::after { + content: ""; + position: absolute; + inset: 18%; + background: url("./images/explosion.svg") center/70% no-repeat; + filter: drop-shadow(0 0 8px rgba(255, 164, 102, 0.8)); } .ship-miss { - width: 50px; - height: 50px; - background-color: rgba(115, 187, 236, 0.112); - content: url(./images/splash-1.svg); - border-radius: 15px; -} - -.square:hover { - background-color: white; + background: radial-gradient( + circle at center, + rgba(98, 246, 255, 0.45) 0%, + rgba(10, 61, 104, 0.65) 55%, + transparent 75% + ), + rgba(9, 25, 45, 0.78); + border: 1px solid rgba(98, 246, 255, 0.35); + box-shadow: 0 0 18px rgba(98, 246, 255, 0.32); + animation: ripple 0.55s ease-out; +} + +.ship-miss::after { + content: ""; + position: absolute; + inset: 20%; + background: url("./images/splash-1.svg") center/65% no-repeat; + filter: drop-shadow(0 0 6px rgba(98, 246, 255, 0.45)); +} + +#announce { + position: absolute; + inset: 0; + display: flex; + align-items: center; + justify-content: center; + pointer-events: none; + align-content: center; + flex-wrap: nowrap; + flex-direction: column; } -.start-game:hover { - background-color: rgb(6, 94, 121); +.announcement { + position: relative; + display: inline-flex; + flex-direction: column; + align-items: center; + z-index: 1000; + justify-content: center; + gap: 12px; + padding: 32px 42px; + border-radius: var(--border-radius); + font-size: 2.4rem; + letter-spacing: 0.1em; + background: linear-gradient( + 145deg, + rgba(255, 139, 167, 0.28), + rgba(98, 246, 255, 0.22) + ), + rgba(13, 26, 53, 0.92); + color: var(--text-strong); + text-shadow: 0 18px 24px rgba(0, 0, 0, 0.35); + box-shadow: 0 28px 48px rgba(0, 0, 0, 0.45); + animation: pulse 0.8s ease-in-out infinite alternate; + pointer-events: auto; } -.start-game:active { - background-color: rgb(19, 17, 17); +.retry { + margin-top: 14px; + padding: 10px 22px; + border-radius: 999px; + z-index: 1001; + background: rgba(98, 246, 255, 0.18); + border: 1px solid rgba(98, 246, 255, 0.4); + color: var(--accent-primary); + font-size: 0.9rem; + letter-spacing: 0.18em; + text-transform: uppercase; + cursor: pointer; + transition: transform 0.18s ease, box-shadow 0.18s ease; + pointer-events: auto; } -.battleship:hover { - background-color: rgb(6, 94, 121); +.retry:hover { + transform: translateY(-2px); + box-shadow: 0 14px 26px rgba(98, 246, 255, 0.28); } -.battleship:active { - background-color: rgb(19, 17, 17); +.retry:active { + transform: translateY(0); } - -.announcement { - position:absolute; - z-index: 1; - margin-top: -90px; - margin-left: -150px; - border: 5px solid rgb(51, 51, 51); - background-color: rgb(173, 173, 196); - border-radius: 20px; - width: 300px; - height: 140px; - padding-bottom:25px; - font-size: 2.5em; - text-align: center; - align-items: center; +footer { + margin-top: 12px; + display: flex; + flex-wrap: wrap; + gap: 16px; justify-content: center; - animation: shake 1s infinite; - animation-direction: alternate; - animation-timing-function: ease-in-out; - box-shadow: 2px 2px 8px 2px black; -} -.retry { - position:absolute; - z-index: 2; - margin-top: 50px; - margin-left: -90px; - border: 5px solid rgb(51, 51, 51); - background-color: rgb(173, 173, 196); - border-radius: 20px; - width: 150px; - height: 45px; - padding: 10px; - font-size: 2em; - text-align: center; align-items: center; - justify-content: center; - box-shadow: 2px 2px 8px 2px black; -} -.retry:hover { - background-color: rgb(6, 94, 121); + padding: 18px 22px; + border-radius: 16px; + background: rgba(9, 16, 33, 0.6); + backdrop-filter: blur(6px); + font-size: 0.85rem; + letter-spacing: 0.08em; + color: var(--text-muted); } -.retry:active { - background-color: white; -} -@keyframes shake { - 0% { transform: translate(1px, 1px) rotate(0deg); } - 10% { transform: translate(-1px, -2px) rotate(-1deg); } - 20% { transform: translate(-3px, 0px) rotate(1deg); } - 30% { transform: translate(3px, 2px) rotate(0deg); } - 40% { transform: translate(1px, -1px) rotate(1deg); } - 50% { transform: translate(-1px, 2px) rotate(-1deg); } - 60% { transform: translate(-3px, 1px) rotate(0deg); } - 70% { transform: translate(3px, 1px) rotate(-1deg); } - 80% { transform: translate(-1px, -1px) rotate(1deg); } - 90% { transform: translate(1px, 2px) rotate(0deg); } - 100% { transform: translate(1px, -2px) rotate(-1deg); } -} - -/* ==== Responsive CSS Styles ==== */ -@media (max-width: 1130px) { - body { - margin: 2px; - } - - .container { - display: flex; - flex-direction: column; - justify-content: space-between; - gap: 10px; - min-height: 400px; - width: 100%; - } - - .game-boards { - height: 100%; - width: 100%; - } - - table { - margin: 0px; - width: 100%; - height: 100%; - } - - .square { - width: 50px; - height: 45px; - } - - .player-board { - border: 1px solid rgb(7, 31, 1); - background-color: rgb(67, 96, 60); - width: 100%; - height: 100%; - font-size: 15px; - justify-content: center; - align-items: center; - align-content: center; - text-align: center; - } - - .computer-board { - border: 1px solid rgb(39, 2, 2); - background-color: rgb(84, 45, 45); - width: 100%; - height: 100%; - font-size: 15px; - justify-content: center; - align-items: center; - align-content: center; - text-align: center; - } - - .battleship { - display: flex; - width: 130px; - justify-content: space-between; - padding: 3px 10px 0px 10px; - } - - .destroyer { - display: flex; - width: 130px; - justify-content: space-between; - padding: 3px 10px 0px 10px; - - } - - .frigate { - display: flex; - width: 130px; - justify-content: space-between; - padding: 3px 10px 0px 10px; - - } - - .patrol-coastal { - display: flex; - width: 130px; - justify-content: space-between; - padding: 3px 10px 0px 10px; - } - - .label { - font-size: 15px; - width: 25px; - padding: 0px; - margin: 0px; - width: 0px; - height: 0px; - } +footer a { + text-transform: uppercase; + font-weight: 600; + letter-spacing: 0.12em; +} - td { - padding: 0px; - width: 25px; - height: 25px; +@keyframes blast { + 0% { + transform: scale(0.7); + opacity: 0.5; } - - .ship { - width: 25px; - height: 25px; - margin: auto; + 100% { + transform: scale(1); + opacity: 1; } +} - .ship-select { - width: 25px; - height: 25px; - margin: auto; +@keyframes ripple { + 0% { + transform: scale(0.6); + opacity: 0.4; } - - .ship-hit { - width: 25px; - height: 25px; - margin: auto; + 100% { + transform: scale(1); + opacity: 1; } +} - .ship-miss { - width: 25px; - height: 25px; - margin: auto; - +@keyframes pulse { + 0% { + box-shadow: 0 0 0 rgba(98, 246, 255, 0.18); + transform: translateY(-4px); } - - .game-boards { - gap: 0px; + 100% { + box-shadow: 0 0 28px rgba(98, 246, 255, 0.35); + transform: translateY(4px); } } - -@media (max-width: 950px) { - body { - margin: 2px; +@media (max-width: 960px) { + :root { + --cell-size: 32px; } .container { - display: flex; - flex-direction: column; - justify-content: space-between; - gap: 10px; - min-height: 400px; - width: 100%; - } - - .game-boards { - flex-direction: column; - height: 100%; - width: 100%; - } - - table { - margin: 0px; - width: 100%; - height: 100%; - } - - .square { - width: 50px; - height: 45px; - } - - .player-board { - border: 1px solid rgb(7, 31, 1); - background-color: rgb(67, 96, 60); - width: 100%; - height: 100%; - font-size: 15px; - justify-content: center; - align-items: center; - align-content: center; - text-align: center; - } - - .computer-board { - border: 1px solid rgb(39, 2, 2); - background-color: rgb(84, 45, 45); - width: 100%; - height: 100%; - font-size: 15px; - justify-content: center; - align-items: center; - align-content: center; - text-align: center; - } - - .battleship { - display: flex; - width: 130px; - justify-content: space-between; - padding: 3px 10px 0px 10px; - } - - .destroyer { - display: flex; - width: 130px; - justify-content: space-between; - padding: 3px 10px 0px 10px; - - } - - .frigate { - display: flex; - width: 130px; - justify-content: space-between; - padding: 3px 10px 0px 10px; - - } - - .patrol-coastal { - display: flex; - width: 130px; - justify-content: space-between; - padding: 3px 10px 0px 10px; - + padding: 24px 0 28px; } - .label { - font-size: 15px; - width: 25px; - padding: 0px; - margin: 0px; - width: 0px; - height: 0px; + .container-main { + grid-template-columns: 1fr; } - td { - padding: 0px; - width: 25px; - height: 25px; + .game-boards { + flex-wrap: wrap; } +} - .ship { - width: 25px; - height: 25px; - margin: auto; +@media (max-width: 768px) { + :root { + --cell-size: 36px; } - .ship-select { - width: 25px; - height: 25px; - margin: auto; + header h1 { + font-size: clamp(2.1rem, 6vw, 2.5rem); } - .ship-hit { - width: 25px; - height: 25px; - margin: auto; - } - - .ship-miss { - width: 25px; - height: 25px; - margin: auto; - - } - - .game-boards { - gap: 0px; + .selection { + padding: 20px; } } - - - - - - - -@media (max-width: 550px) { +@media (max-width: 700px) { body { - margin: 2px; + align-items: flex-start; + padding: 24px 0; } +} - .container { - display: flex; - flex-direction: column; - justify-content: space-between; - gap: 10px; - min-height: 400px; - width: 100%; +@media (max-width: 520px) { + :root { + --cell-size: 40px; } - .game-boards { - flex-direction: column; - height: 100%; - width: 100%; + .container { + gap: 20px; } - table { - margin: 0px; - width: 100%; - height: 100%; + .message { + padding: 16px; } - .player-board { - border: 1px solid rgb(7, 31, 1); - background-color: rgb(67, 96, 60); - width: 100%; - font-size: 15px; - justify-content: center; - align-items: center; - align-content: center; - text-align: center; + .ship-selection1, + .ship-selection2 { + grid-template-columns: 1fr; } - .computer-board { - border: 1px solid rgb(39, 2, 2); - background-color: rgb(84, 45, 45); - width: 100%; - font-size: 15px; - justify-content: center; - align-items: center; - align-content: center; + footer { text-align: center; } - - .battleship { - display: flex; - width: 130px; - justify-content: space-between; - padding: 3px 10px 0px 10px; - } - - .destroyer { - display: flex; - width: 130px; - justify-content: space-between; - padding: 3px 10px 0px 10px; - - } - - .frigate { - display: flex; - width: 130px; - justify-content: space-between; - padding: 3px 10px 0px 10px; - - } - - .patrol-coastal { - display: flex; - width: 130px; - justify-content: space-between; - padding: 3px 10px 0px 10px; - - } - - .label { - font-size: 15px; - width: 25px; - padding: 0px; - margin: 0px; - width: 0px; - height: 0px; - } - - td { - padding: 0px; - width: 25px; - height: 25px; - } - - .square { - width: 25px; - height: 25px; - } - - .ship { - width: 25px; - height: 25px; - margin: auto; - } - - .ship-select { - width: 25px; - height: 25px; - margin: auto; - } - - .ship-hit { - width: 25px; - height: 25px; - margin: auto; - } - - .ship-miss { - width: 25px; - height: 25px; - margin: auto; - - } - - .game-boards { - gap: 0px; - } -} \ No newline at end of file +} diff --git a/battleship.js b/battleship.js index 27a00d2..3f86754 100644 --- a/battleship.js +++ b/battleship.js @@ -1,776 +1,56 @@ -var gameStart = false; -var validHit = true; -//AI Switches for smart attacks -var automatedAttackUp = false; -var automatedAttackDown = false; -var automatedAttackLeft = false; -var automatedAttackRight = false; -var initialCpuHit; -var savedAIAttackVector; - -// =========== arrays to represent gameboard==================================== -var gameBoard = [ - [0, 1, 2, 3, 4, 5, 6, 7, 8, 9], - [10, 11, 12, 13, 14, 15, 16, 17, 18, 19], - [20, 21, 22, 23, 24, 25, 26, 27, 28, 29], - [30, 31, 32, 33, 34, 35, 36, 37, 38, 39], - [40, 41, 42, 43, 44, 45, 46, 47, 48, 49], - [50, 51, 52, 53, 54, 55, 56, 57, 58, 59], - [60, 61, 62, 63, 64, 65, 66, 67, 68, 69], - [70, 71, 72, 73, 74, 75, 76, 77, 78, 79], - [80, 81, 82, 83, 84, 85, 86, 87, 88, 89], - [90, 91, 92, 93, 94, 95, 96, 97, 98, 99], -]; - -var grid = [ - [0, 1, 2, 3, 4, 5, 6, 7, 8, 9], - [0, 1, 2, 3, 4, 5, 6, 7, 8, 9], - [0, 1, 2, 3, 4, 5, 6, 7, 8, 9], - [0, 1, 2, 3, 4, 5, 6, 7, 8, 9], - [0, 1, 2, 3, 4, 5, 6, 7, 8, 9], - [0, 1, 2, 3, 4, 5, 6, 7, 8, 9], - [0, 1, 2, 3, 4, 5, 6, 7, 8, 9], - [0, 1, 2, 3, 4, 5, 6, 7, 8, 9], - [0, 1, 2, 3, 4, 5, 6, 7, 8, 9], - [0, 1, 2, 3, 4, 5, 6, 7, 8, 9], -]; - -var cpuBoardSelection = [ - ["x", "x", "x", "x", "x", "x", "x", "x", "x", "x"], - ["x", "x", "x", "x", "x", "x", "x", "x", "x", "x"], - ["x", "x", "x", "x", "x", "x", "x", "x", "x", "x"], - ["x", "x", "x", "x", "x", "x", "x", "x", "x", "x"], - ["x", "x", "x", "x", "x", "x", "x", "x", "x", "x"], - ["x", "x", "x", "x", "x", "x", "x", "x", "x", "x"], - ["x", "x", "x", "x", "x", "x", "x", "x", "x", "x"], - ["x", "x", "x", "x", "x", "x", "x", "x", "x", "x"], - ["x", "x", "x", "x", "x", "x", "x", "x", "x", "x"], - ["x", "x", "x", "x", "x", "x", "x", "x", "x", "x"], -]; - -var playerBoardSelection = [ - ["x", "x", "x", "x", "x", "x", "x", "x", "x", "x"], - ["x", "x", "x", "x", "x", "x", "x", "x", "x", "x"], - ["x", "x", "x", "x", "x", "x", "x", "x", "x", "x"], - ["x", "x", "x", "x", "x", "x", "x", "x", "x", "x"], - ["x", "x", "x", "x", "x", "x", "x", "x", "x", "x"], - ["x", "x", "x", "x", "x", "x", "x", "x", "x", "x"], - ["x", "x", "x", "x", "x", "x", "x", "x", "x", "x"], - ["x", "x", "x", "x", "x", "x", "x", "x", "x", "x"], - ["x", "x", "x", "x", "x", "x", "x", "x", "x", "x"], - ["x", "x", "x", "x", "x", "x", "x", "x", "x", "x"], -]; - -var cpuAttackLog = [ - ["x", "x", "x", "x", "x", "x", "x", "x", "x", "x"], - ["x", "x", "x", "x", "x", "x", "x", "x", "x", "x"], - ["x", "x", "x", "x", "x", "x", "x", "x", "x", "x"], - ["x", "x", "x", "x", "x", "x", "x", "x", "x", "x"], - ["x", "x", "x", "x", "x", "x", "x", "x", "x", "x"], - ["x", "x", "x", "x", "x", "x", "x", "x", "x", "x"], - ["x", "x", "x", "x", "x", "x", "x", "x", "x", "x"], - ["x", "x", "x", "x", "x", "x", "x", "x", "x", "x"], - ["x", "x", "x", "x", "x", "x", "x", "x", "x", "x"], - ["x", "x", "x", "x", "x", "x", "x", "x", "x", "x"], -]; - -var playerAttackLog = [ - ["x", "x", "x", "x", "x", "x", "x", "x", "x", "x"], - ["x", "x", "x", "x", "x", "x", "x", "x", "x", "x"], - ["x", "x", "x", "x", "x", "x", "x", "x", "x", "x"], - ["x", "x", "x", "x", "x", "x", "x", "x", "x", "x"], - ["x", "x", "x", "x", "x", "x", "x", "x", "x", "x"], - ["x", "x", "x", "x", "x", "x", "x", "x", "x", "x"], - ["x", "x", "x", "x", "x", "x", "x", "x", "x", "x"], - ["x", "x", "x", "x", "x", "x", "x", "x", "x", "x"], - ["x", "x", "x", "x", "x", "x", "x", "x", "x", "x"], - ["x", "x", "x", "x", "x", "x", "x", "x", "x", "x"], -]; - -// ================ Randomizer Functions for Computer ========================== -//creates random row number -var randomRow = () => { - row = Math.floor(Math.random() * 10); - return row; -}; -// creates random column number -var randomColumn = () => { - column = Math.floor(Math.random() * 10); - return column; -}; -// Randomly determines if ship will be verticle or horizontal -var randomVert = () => { - random = Math.floor(Math.random() * 2); - if (random == 0) { - return false; - } else { - return true; - } -}; - -//========= Validation Functions =============================================== -// Checks to see if ship can fit on board. -var checkBoard = (ship) => { - for (var i = 0; i < ship.length; i++) { - for (var j = 0; j < gameBoard.length; j++) { - for (var k = 0; k < gameBoard[j].length; k++) { - if (ship[i] == gameBoard[j][k] && cpuBoardSelection[j][k] == 1) { - return false; - } - } - } - } - //if ship fits, return true - return true; -}; - -//checks to see in random numbers are in bounds for ship -var inBounds = (shipLength, row, column, vert) => { - var rightBoundary = 10; - var bottomBoundary = 10; - - if (!vert) { - if (grid[row][column] + shipLength > rightBoundary) { - return false; - } else { - return true; - } - } else { - if (grid[row + shipLength] == undefined) { - return false; - } else { - return true; - } - } -}; - -//================= Ship Creation Functions ==================================== -//creates verticle ship -var createVertShip = (shipLength, row, column) => { - var ship = []; - for (var i = 0; i < shipLength; i++) { - ship.push(gameBoard[row + i][column]); - } - return ship; -}; - -//creates horizontal ship -var createHorizShip = (shipLength, row, column) => { - var ship = []; - for (var i = 0; i < shipLength; i++) { - ship.push(gameBoard[row][column + i]); - } - return ship; -}; - -//Marks Board with 1's to show where ships are to be placed -var markBoard = (ship) => { - for (var i = 0; i < ship.length; i++) { - for (var j = 0; j < gameBoard.length; j++) { - for (var k = 0; k < gameBoard[j].length; k++) { - if (ship[i] == gameBoard[j][k]) { - cpuBoardSelection[j][k] = 1; - } - } - } - } -}; - -//============= CPU Ship building function ===================================== -//Calls other functions to make sure ship can be built -var cpuBuildShip = (shipLength) => { - //builds ship to users specification - var shipAvailable = false; - while (!shipAvailable) { - var isInBounds = false; - while (!isInBounds) { - var row = randomRow(); - var column = randomColumn(); - var vert = randomVert(); - isInBounds = inBounds(shipLength, row, column, vert); - } - var ship = []; - if (!vert) { - ship = createHorizShip(shipLength, row, column); - } else if (vert) { - ship = createVertShip(shipLength, row, column); - } - - //stops loop if ship can be built at location - shipAvailable = checkBoard(ship); - } - //places ships on board - markBoard(ship); - return ship; -}; - -// Displays data of CPU's created ship on webpage -var cpuDisplayShip = (ship) => { - for (var i = 0; i < ship.length; i++) { - document.getElementById("A" + ship[i]).innerHTML = - "
"; - } -}; - -// ========= Player Ship Building Functions ==================================== -// Displays data of CPU's created ship on webpage -var playerDisplayShip = (ship) => { - for (var i = 0; i < ship.length; i++) { - document.getElementById(ship[i]).innerHTML = - ""; - } -}; - -//Marks Board with 1's to show where ships are to be placed -var markPlayerBoard = (ship) => { - for (var i = 0; i < ship.length; i++) { - for (var j = 0; j < gameBoard.length; j++) { - for (var k = 0; k < gameBoard[j].length; k++) { - if (ship[i] == gameBoard[j][k]) { - playerBoardSelection[j][k] = 1; - } - } - } - } -}; - -// Checks player's board -var checkPlayerBoard = (ship) => { - for (var i = 0; i < ship.length; i++) { - for (var j = 0; j < gameBoard.length; j++) { - for (var k = 0; k < gameBoard[j].length; k++) { - if (ship[i] == gameBoard[j][k] && playerBoardSelection[j][k] == 1) { - return false; - } - } - } - } - //if ship fits, return true - return true; -}; - -// finds row of player click on board -locateBoardRow = (id) => { - for (var i = 0; i < gameBoard.length; i++) { - for (var j = 0; j < gameBoard[i].length; j++) { - if (id == gameBoard[i][j]) { - console.log("i = " + i); - return i; - } - } - } -}; - -// finds column of player click on board -locateBoardColumn = (id) => { - for (var i = 0; i < gameBoard.length; i++) { - for (var j = 0; j < gameBoard[i].length; j++) { - if (id == gameBoard[i][j]) { - console.log("j = " + i); - return j; - } - } - } -}; - -// detects if player's ship is completely on the board -var playerInBounds = (shipLength, row, column, vert) => { - var rightBoundary = 10; - var bottomBoundary = 10; - - if (!vert) { - if (grid[row][column] + shipLength > rightBoundary) { - return false; - } else { - return true; - } - } else { - if (grid[row] + shipLength == undefined) { - // Investigate further - return false; - } else { - return true; - } - } -}; - -//========= Player Ship Count Functions ========== -// Sets the limit for amount ships player can have -var playerBattleship = 1; -var playerDestroyer = 2; -var playerFrigate = 1; -var playerCoastalShip = 1; - -// Reduces available number of ships when player places one on board -// Notifies player when all of a ship class has been placed on board. -var checkShipClassAvail = (shipSize) => { - if (shipSize == 5) { - if (playerBattleship > 0) { - playerBattleship--; - document.getElementById("Battleship").innerHTML = " " + playerBattleship; - return true; - } else { - alert("All Battleships placed on board. Select a different ship."); - } - } else if (shipSize == 4) { - if (playerDestroyer > 0) { - playerDestroyer--; - document.getElementById("Destroyer").innerHTML = " " + playerDestroyer; - return true; - } else { - alert("All Destroyers placed on board. Select a different ship."); - } - } else if (shipSize == 3) { - if (playerFrigate > 0) { - playerFrigate--; - document.getElementById("Frigate").innerHTML = " " + playerFrigate; - return true; - } else { - alert("All Frigates placed on board. Select a different ship."); - } - } else if (shipSize == 2) { - if (playerCoastalShip > 0) { - playerCoastalShip--; - document.getElementById("Patrolship").innerHTML = " " + playerCoastalShip; - return true; - } else if (playerCoastalShip == 0 && !gameStart) { - alert( - "All Patrol Coastal Ships placed on board. Select a different ship." - ); - } - } -}; - -// builds player's ship -var playerBuildShip = (shipLength, id) => { - if (shipLength > 0) { - //builds ship to users specification - var shipAvailable = false; - var isInBounds = false; - var ship = []; - - var row = locateBoardRow(id); - var column = locateBoardColumn(id); - - if (!vert) { - ship = createHorizShip(shipLength, row, column); - } else if (vert) { - ship = createVertShip(shipLength, row, column); - } - - //checks to see if ship is in bounds and available to place on board - shipAvailable = checkPlayerBoard(ship); - isInBounds = playerInBounds(shipLength, row, column, vert); - - if (shipAvailable && isInBounds) { - var shipCountGood = checkShipClassAvail(shipLength); - - if (shipCountGood) { - //places ships on logical board - markPlayerBoard(ship); - return ship; - } - } else { - if (!gameStart) { - alert("Ship out of bounds!"); - } - } - } -}; - -var playerHits = 0; -var cpuHits = 0; -// checks player missile strike location for ship presence -// notifies player of direct hit if ship present, changes logical board marker -// at location from 1 to 0, notifies player if he has already fired missile at location. -var checkIfDirectHit = (id) => { - for (var i = 0; i < gameBoard.length; i++) { - for (var j = 0; j < gameBoard[i].length; j++) { - if (id == gameBoard[i][j] && cpuBoardSelection[i][j] == 1) { - cpuBoardSelection[i][j] = 0; - document.getElementById("A" + id).innerHTML = - ""; - validHit = true; - return true; - } else if (id == gameBoard[i][j] && cpuBoardSelection[i][j] == 0) { - validHit = false; - } else if ( - id == gameBoard[i][j] && - cpuBoardSelection[i][j] == "x" && - playerAttackLog[i][j] == "x" - ) { - playerAttackLog[i][j] = 0; - document.getElementById("A" + id).innerHTML = - ""; - validHit = true; - } else if (id == gameBoard[i][j] && playerAttackLog[i][j] == 0) { - validHit = false; - } - } - } -}; - -var checkCpuHits = () => { - if (cpuHits == 18) { - // Notifies player they lost - cpuHits++; - gameStart = false; - document.getElementById("announce").innerHTML = - "