Skip to content

About

This is just a fun project creating a game of battleship. Experimenting with functional programming.

Resources

Stars

0 stars

Watchers

1 watching

Forks

Latest commit

 

History

584 Commits

Folders and files

NameName
Last commit message
Last commit date
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 

Repository files navigation

battleship

This is just a fun project creating a game of battleship. Experimenting with functional programming and ES6 features.

A relatively recent version is running at https: //joshuaheagle.com/battleship/

Running locally

Solo, hot-seat and robots-only play are just the static page - nothing to run, open the built page (e.g. http://battleship.local) in a browser.

Online Multiplayer additionally needs the lobby server running:

npm run serve:lobby

This compiles and starts server/lobbyServer.ts on port 3002 (src/network/lobbySocket.ts's own DEFAULT_URL). The page itself still needs to be served separately (same as local play) - this is just the socket.io server it talks to for room hosting/joining and the remote game itself.

Members

hasTrait

The typed version of json-dom's hasTrait, it narrows an item to the trait it was checked for.

PLACEMENT_TIMEOUT_MS

How long players have to finish placing before any still-pending ships are placed at random for them.

lastRoomState

The room's own last known state (its code, name, host, and players) - kept up to date by renderRoomState below regardless of whether the waiting room is currently shown, so Play Again (see remotePlayAgainListener.ts) can show it again for everyone once a game ends, without a fresh server round trip to ask for it again.

pendingJoinCode

The room code a successful peek (see peekAndConfirm) found - remembered so the Join confirm panel's own submit can join it without needing its own room-code field.

playerColours

The colour each player is identified by, in the order they are created. They are bright enough to read on the dark background, and distinct from one another.

defaultShipSpecs

Create a default fleet using the standard battleship lengths.

STAGE_MS

How long each stage of the robot's attack is shown for, in turn.

DAMAGED_WEIGHT

Extra weight for placements which explain a ship that is already damaged, relative to a fresh ship.

ADJACENT_WEIGHT

Extra weight for an unattacked cell next to a hit on a ship which is not yet sunk, the partial-hit follow-up.

Constants

DEFAULT_URL

No server is deployed yet - this only works against a locally-run lobby server (npm run dev:lobby).

setViewShip

Set a visible ship part at the given coordinates (shown with a grey background).

setHit

Mark the cell at the given coordinates as hit.

setHiddenShip

Set a hidden ship part at the given coordinates (not shown, the default cell styling still applies).

ATTACKER_LOCK_MS

How long a human attacker's own lock stays on once their turn starts. This used to be 400ms to match a board-resize animation (sass/game-pieces.sass's old per-turn .matrix resize, confirmed via git history - d1be1695); that resize was removed later and nothing on .player itself has ever had a CSS transition to wait out, so the original value outlived the thing it was timed to. It stays non-zero, just much shorter, for the one real job it still does in local hot-seat: attackFleet.ts's guard only blocks attacking the current attacker's own board, so without this lock a human could rapid-click several different victims inside what should be a single turn. Remote play already blocks the same thing independently, by socket identity (lobbyServer.ts's gameAction handler), so this is redundant there - but it costs nothing to keep uniform.

Functions

queueTimeout(item, fn, time, ...args)

The timed queue the given item's game runs on: steps queued here run one after another, after their delay. Each game has its own queue (see gameSession), so two games' turn changes, robot attacks, and animations never interleave.

getLowStatusItems(items)

Given an array of items, return all items which have the lowest status property

getBrokenShipsPlayers(players)

Return all of the players which have broken ships.

getBrokenItems(items)

Given an array of items, return all of the items which have a status less than 100, but more than 0

getAllNonHitCells(matrix)

Get all points which were not yet hit in the matrix.

getALowStatusItem(items)

Given an array of items, return the item with the lowest status property (at the end of the array)

filterAdjacentPoints(pnt)

Used to generate 'checkerboard' style attack by only attacking every non-edge-touching cell

checkIfShipCell(pnt, matrix)

Return the hasShip tile boolean at the specified point.

checkIfHitCell(pnt, matrix)

Return the isHit tile boolean at the specified point.

clearBody(parent)

Remove everything from the page: whatever screen was showing (the menu, a finished game's final scores, or a game in progress), so a new one can be built on a blank page.

applyInitialLocalView(players, mode)

Set every player's role for robots-only/watch, the one mode whose view never changes by turn at all - nobody is ever "own", so there is nothing for updatePlayer.ts's own per-turn hook to recompute (see its own comment). Solo's "own" viewer is also fixed (always the human) but its opponents' target/summary split still depends on whose turn it actually is (robots attack each other too, not just the human), so solo is recomputed on every turn the same way hot-seat is - see updatePlayer.ts - not set up here. Remote's own per-socket roles are applied separately, per viewer, by redactGameState.ts - never here, on the one shared server tree.

startRound(order, firstGoesFirst)

Pick the first attacker, and let a robot start if it is one.

startNewGame(parent, humans, robots, firstGoesFirst, hints, placementStarter, onPlayersBuilt)

Build the players, place their ships, and start the round. Shared by beginRound (reading these settings from the lobby form) and playAgain (reading them from the settings the last game was started with) - either way, this is the one place a round actually begins. A remote game passes startRemotePlacement (simultaneous per-player placement, no handoff) in place of the default, and onPlayersBuilt to set each player's real name before anything is ever rendered or pushed - see server/gameplay.ts. Local play needs neither: a human types their own name during their own handoff screen (see placement.ts's nameInput), since there is nothing to know upfront.

startMenu(parent)

The entry function

update(item, attributes)

Update an item's attributes in place on the real element.

show(item, shown)

Show or hide an item by its display style.

showLobby(menu, preset, values)

Show the lobby for a game type, inside an already-rendered main menu: sets its title and field limits from the preset, fills in the given values (or the preset's own defaults), and reveals it in place of the game types.

returnToLobby(e, button)

Back to the lobby, with the settings from the game that just ended already filled in, so they can be changed before playing again - unlike the main menu button, which goes all the way back to choosing the game type.

restart(e, button)
remotePlayAgainListener(e, target)

The final-score screen's Play Again button, for a remote game - any player can click it (there is nothing destructive about asking to see the waiting room again), not just the host. Leaves the finished game (stopping forwarding, clearing the rendered tree - see remoteGame.ts's leaveRemoteGame) and shows the same room's own waiting room again, with the same players and the same host, using the room's own last known state (see remoteListener.ts's getLastRoomState - kept current throughout, not just while the waiting room is visible, so no fresh server round trip is needed here). From there, the host starts a new round exactly the way they started the first one - server/lobbyServer.ts's startGame handler already allows a room's own game to restart once it has actually ended.

This only ever runs client-side, with forwarding already turned off for this exact reason (see remoteGame.ts's enterRemoteGame) - the server's own copy of this listener name is a trivial stand-in, never meant to actually run.

remotePlacementListener(e, target)

The remote placement/ordering panels' own buttons - each player's Randomise/Ready, and the host-only Random/ Set order. Told apart by class name, same pattern as local placement's own placementListener.

isRemoteSessionActive()

Whether a remote placement/ordering phase is running for this item's game - so a board click during it is routed here instead of to a normal attack (see attackListener.ts).

cancelRemotePlacement()

Cancel this game's own placement/ordering session, if one is still running, clearing its pending auto-finish timer and forgetting it. Without this, a room abandoned (or ended some other way, e.g. onGameOver - see server/gameplay.ts) while still placing/ordering leaves its own 60-second timer pending; unref() keeps that alone from holding the process open, but it can still fire later against an already-torn-down root, crashing on whatever stale assumption autoFinishPlacement/beginOrdering makes about its shape. Safe to call even when no session is running (normal completion already deletes its own via finishOrdering).

isHostOnlyStage()

Whether only the host may act right now - every stage except placing itself (see server/lobbyServer.ts).

remotePlacementStage()

The current remote placement/ordering stage for this item's game, or null once the round has actually started (no session is running any more - see finishOrdering, which deletes it). redactGameState.ts's own redactGameBody reads this to decide whether a viewer's own push should include every player (ordering, where the host needs to see and click everyone) or only their own (placing itself, see the plan this served - every other player's board/fleet/placement panel never need to reach a still-placing viewer).

renderPanel()

Refresh one player's own placement panel to match their current state.

showStart()

Mark which cells a ship-in-progress could end on, same visual as local placement's own showStart.

startRemotePlacement(players, body, done, onTimerChange)

Begin a remote game's placement phase: every human places their own ships on their own board at the same time - no handoff, no "look away". done runs once the order is set, with the order play will take. onTimerChange runs whenever the deadline below actually fires and changes state on its own - every other state change in this module happens inside a function a dispatched/forwarded click calls directly, which the server's own broadcast-after-dispatch already covers; the deadline is the one change that happens on a raw timer with nothing else watching for it, so without this hook a client that lets it expire sees nothing at all, even though the server's own state has already moved on. A robot filling an empty remote seat (see server/gameplay.ts's startRoomGame) already has its fleet placed by buildPlayers.ts, same as local play's own robots - it is seeded ready here immediately, never waiting on a ready-up that nothing would ever drive, and never gets a placement panel of its own.

placeRemoteShip(tile, board)

A click on a player's own board during placement: the first click sets where a ship starts, the second where it ends (an invalid second click is refused and the start is forgotten) - exactly local placement's own two-click mechanic, just resolved from the clicked board's own owner instead of a shared "current player".

pickOrderPlayer()

A click on a player's own board while the order is being set (see beginOrderSet) - adds them to the order.

handleRemoteBoardClick()

A board click while a remote placement/ordering phase is running - routed by the current stage.

beginOrdering()

Once every player is ready (or the timer below fires), hide their panels and show the ordering choice.

autoFinishPlacement()

Any player still not ready when the deadline passes has their remaining ships placed for them at random.

readyRemotePlayer()

The Ready button: locks a player's own fleet in once nothing is left pending.

randomiseRemoteShips()

The Randomise button: re-rolls just this player's own remaining fleet, same as local placement's own version.

beginOrderSet()

The Set order button: from here, clicking each player's board in turn (see pickOrderPlayer) sets the order.

highlightOnly()

Outline one player's panel, and clear the outline from the others - the random order's own shuffle animation.

shuffle()

A shuffled copy of the players, in a random order - identical to local placement's own version.

chooseOrderRandom()

The Random button: a short highlight passes over the players, then lands on a full random order.

finishOrdering()

The order is set (either way) - show it briefly, then begin the round.

getLastRoomState()

The current room's own last known state, if this connection has ever been in one.

showSubPanel()

Show exactly one of remote-entry's own four sub-panels (see mainMenu.ts), hiding the other three.

peekAndConfirm()

Look up a room by code (without joining it - see lobbySocket.ts's peekRoom) and move to the Join confirm panel showing whose game it is, or fall back to the code-entry panel with the error shown inline if it could not be found. The code-entry panel (with the code already filled in) shows immediately, before the lookup even resolves, so a direct link lands on something straight away rather than a blank wait.

showRemoteEntry()

Show the remote entry area in place of the game-type tiles - either the Host/Join choice (no code yet, the Online Multiplayer tile itself), or (a shared join link - see main.ts) straight to the Join confirm panel once that code's own room has been found.

renderRoomState()

Replace the waiting room's player list, lobby name, room code and host controls with a freshly-received room state.

leaveToPresets()

Leave whatever room is open and show the game types again, clearing any status message - or, with a message (the room closed on its own), show it on the Host/Join choice panel instead of the tiles.

enterWaitingRoom()

Once a room is created or joined (or a finished game's Play Again brings everyone back to it - see remotePlayAgainListener.ts), watch it for changes and show the waiting room. Puts the room's own code in the address bar too, so the host (or anyone else) can just copy the current URL to share a join link - see showRemoteEntry, which reads it back out on the receiving end.

remoteListener(e, target)

The Online Multiplayer tile, its Host/Join choice and forms, and the waiting room it leads to. Room/presence only - actual gameplay over the socket is a separate, later piece.

remoteLeaveListener(e, target)

The final-score screen's Leave button, for a remote game - any player can click it, same as Play Again (there is nothing destructive about leaving). Unlike Play Again, this takes just its own clicker all the way back to the main menu: leaves the finished game (stopping forwarding, clearing the rendered tree - see remoteGame.ts's leaveRemoteGame), disconnects this connection's own lobby socket and clears the shared ?room= link from the address bar (the same cleanup remoteListener.ts's own leaveToPresets does for the waiting room's Leave button), and shows a freshly rendered menu - whose default screen is the game-type tiles, not the waiting room.

This only ever runs client-side, with forwarding already turned off for this exact reason (see remoteGame.ts's enterRemoteGame) - the server's own copy of this listener name is a trivial stand-in, never meant to actually run (see server/gameplay.ts's buildIsolatedRoot).

presetListener(e, target)

The game types on the entry screen. Each reveals the lobby for that type (see showLobby). Back hides the lobby again. Presets (and Back) are told apart by their class names, so one listener handles all of them.

playerColour(index)

The colour for the player at this position in the game.

playAgain(e, button)

Play again with the same settings as the game that just ended: the same humans, robots, who goes first, and hint setting. Only the settings carry over, not the fleets or the board state - a new game still places its ships, for multiplayer same as the first time.

placementListener(e, target)

The placement buttons: Continue (after the handoff), Randomise, and Done. They are told apart by class name.

setBadge(player, text)

The label above a player's board, which shows their place in the order.

nameInput()

The name field on the placement panel.

readName(session, player)

Save the name typed for a player, if one was typed. The default name (Player N) is kept otherwise.

isPlacing()

Whether a placement phase is running for this item's game, so board clicks are placements rather than attacks.

startPlacement(players, body, done)

Show the panel and start with the first human's handoff. done runs once the round is ready to start, with the order the players will take turns in. With several players that order is chosen here; otherwise it is the seat order.

setStatsShown(players, shown)

Show or hide every player's stats (health, and the hint checkbox), which are not wanted during placement.

showOnly(session, player)

Show only this player's board, or with null hide every board.

showAll()

Show every board, so the players can see and click each other's.

continueTurn(item)

Continue: from a handoff it starts that player's placement; from the ready screen it starts the round.

showStart(session, point, size)

Show where a ship has started, and the cells it could end on: every cell in a straight line from the start which would be a valid placement. Clicking the start again, or any other cell which is not valid, cancels the start. With no start (null) every mark is removed.

endCount(session, start, size)

The number of cells a ship of this size could end on from the start, so the message can say when there are none.

placeCell(tile, board)

A click on a board: during placement, the first click sets where a ship starts and the second where it ends (an invalid second click is refused and the start is forgotten). While the order is being set, a click picks that player.

randomise(item)

Clear the current player's board, then place their whole fleet at random.

hideShips(player)

Hide a player's ships again: during placement they are shaded so the player can see them, and in play they must not be.

showReady(session)

Every human has placed. With several players, the boards are shown again and they choose who goes first. With one human, or robots only, the boards are hidden and everyone is asked to confirm. The ships are cleared while no board is showing, so they cannot be seen fading out.

chooseOrder(item)

Set the order by clicking the boards: each click adds that player to the end of the order.

ordinal(place)

The word for a place in the order: 1st, 2nd, 3rd, then 4th and so on.

pickPlayer(session, board)

Add a clicked board's player to the end of the order, and finish once everyone is in it.

showOrder(session)

Show the order which has been set, and wait for Continue to start.

randomOrder(item)

Random: a short highlight passes over the players, then lands on a full random order.

shuffle(players)

A shuffled copy of the players, in a random order.

highlightOnly(session, chosen)

Outline one player's panel, and clear the outline from the others.

reorderBoards(order)

Put the boards in turn order, so the page reads in the order play will go in, and turns follow it. The game's tree and the page are both reordered: moving a panel with json-dom would detach it from the page.

startRound(session, order)

The round starts: every board and its stats are shown in turn order, and the placement panel goes.

finishTurn(item)

The player is happy with their fleet: the next human places, or, after the last, everyone is asked to confirm.

isValidPlacement(board, start, end, length)

Whether a ship of this length can go from start to end: a straight horizontal or vertical line of exactly that many cells, inside the board, and not touching a ship which is already there.

placeShip(board, shipInfo, start, end, view)

Place a ship from the player's chosen start and end points, if the placement is valid. Returns false if it is not.

validPlacements(matrix, shipLength)

Every straight, horizontal or vertical, start and end point of a ship of this length which fits on the board without touching a ship already there. Ships only go along one axis for now (no diagonals, z is always 0) - matrix-dom's getLinesOfLength enumerates every line the geometry allows, and only knows about shape, not what occupies a cell, so the blocked-cell filter (and any future direction, if diagonal or 3D ships ever arrive) stays here.

generateStartEnd(matrix, shipLength)

Pick a start and end point for a ship of the given length, at random from every placement that fits. Throws if no placement fits, rather than searching forever.

generateRandomFleet(ships, matrix, view)

Create a series of randomly placed ships based on the provided shipLengths. The optional parameter view will set the visibility of the ships.

getSession(item)

The session for whichever game item belongs to - any DomItem in that game's tree, or its root itself, works the same way. A game gets its own session the first time anything asks for it, and it is garbage-collected along with its root once nothing else references the game any more - there is nothing to explicitly tear down.

presetForMode(mode)

The preset for a given game mode, so the lobby can be shown for it without a preset button having been clicked.

buildShip(shipInfo, line, matrix, view)

Generate a ship with the provided line of points. The visibility of the ship on the board is determined by the view parameter.

buildPlayers(humans, root, robots, players)

Create players and associated properties. Takes an integer for the number of players to generate. Returns an array of players. WARNING: This is a recursive function.

beginRound(e, mainForm)

Logic for setting up and starting a new round from the lobby form.

remainingHitPoints(player)

The hits still needed to sink a player's unsunk ships: the unhit parts of every ship which is not yet sunk.

best(players, score, highest)

The players with the lowest score, or with the highest when highest is set.

afloat(players)

The players still afloat, or everyone when none is.

eliminationRule(players)

Elimination first: attack the player with the fewest hits still needed to sink everything they have left, so the robot knocks them out soonest. Ties are picked at random. This is the rule the game uses.

hitChanceRule(players)

Hit chance first: attack the player whose best cell has the highest chance of holding a ship part. Not used by the game yet. In simulation it prolongs games, because it favours boards with more ship left, but a game style which scores hits may want it.

selectTargetPlayer(players, rule)

Choose which player to attack, using the rule for the game style (elimination for now).

selectTargetCoordinate(victim)

Choose which coordinate to attack, in layers: the density model first, then the checkerboard over every unattacked cell. The density scores are shown as a heat map, so the robot's thinking can be seen before the checkerboard narrows the choice.

shade(intensity)

A faint yellow for the weakest cells up to a solid one for the strongest, so the spread of the robot's thinking shows.

paint(victim, point, color)
resetTargets(data)
outlineBoard(player, color)

Colour every row of a player's board, which is how a whole board is outlined.

displayTargets(cells, target, victim, opponents)

The stages of the robot's thinking, shown in turn: the boards it is choosing between, the chosen board, the cells it weighs, then the cell it picks at random. The shot itself comes after the last stage (see computerAttack).

clearTargets(victim, opponents)

Take the display away once the robot has chosen: the heat map and every board outline.

buildShotState(victim)

Build what the robot is allowed to know about a victim: which cells were attacked, which of those were hits (the hit parts of each ship, not the position of any part still unhit), and how many parts each unsunk ship has. Hit or miss is read from the ship parts' isHit flags, never from a tile's hasShip.

refineTies(cells)

Among cells tied at the top score, prefer the checkerboard pattern used to find ships quickly. The partial-hit follow-up is not handled here: it is extra weight inside scoreTargets, so it is already part of the score.

densityChoices(victim)

The density model's picture of the board: every unattacked cell with a score, shaded by how far it is from the top score (heat, for display), and the cells the robot chooses from (targets: the highest, narrowed to the checkerboard among them). targets is never empty while a ship remains.

densityTargets(victim)

The attack points the density model considers most likely to hold a ship part, or an empty array when there is none.

bestHitChance(victim)

The best chance that a shot at this player's board hits a ship: the highest per-cell hit chance.

consistent(placement, shipHitKeys, missKeys, hitKeys)

Whether a placement could be where a ship really is: it avoids misses, covers all of the ship's own known hits, and touches no other ship's hit.

scoreTargets(state, damagedWeight, adjacentWeight)

Score every cell by how many ways the remaining ships could still cover it. Each ship contributes every placement consistent with what is known: it avoids misses, covers all of its own known hits, and touches no other ship's hit. Cells already attacked score zero. Unattacked cells next to a hit on an unsunk ship then get the adjacent weight on top.

bestTargets(scores)

The unattacked cells which have the highest score. Empty when no score is above zero.

hitChances(state, damagedWeight)

The chance that each unattacked cell holds a part of some remaining ship. Each ship's placements are weighted as in scoreTargets, then turned into a share of that ship's total, so a ship counts once however many placements it has. Not used by the game's robot yet: it is kept for game styles where the chance of a hit matters more than elimination.

computerAttack(player, players)

Main AI logic for computer to attack, selects a target then performs attack function.

clearChildren()

Remove every one of a parent's children - the same pattern startNewGame's clearBody uses locally.

deadlineOf()

Whichever deadline a pushed body actually carries right now, placement or turn - an empty string (the lingering, no-longer-relevant state left behind once that phase ends) is falsy, same as it being absent altogether, so both read the same way here.

isGameOver()

Whether a pushed body is the final-score screen (see remoteFinalScore.ts) - the one point in a remote game's own lifecycle where forwarding has to come back off, so its Play Again button's click runs as a real local listener instead of being forwarded into a game that is already over.

finalScoreOnly()

A pushed body, reduced to just its final-scores child - server/gameplay.ts's own onGameOver hook already clears everything else before a game-over push ever goes out, but rendering only ever what this module itself has confirmed is the final-score screen, regardless of whatever else might be sitting alongside it, means a future regression upstream (something left in the body that still references a listener name the real client only ever registers as a forwarder, like remotePlacementListener) clears the whole page with nothing on it, instead of just failing to show the one thing that is actually safe to show.

ensureCountdownElement()

Create the countdown element if there is not already a live one in the page - not just a non-null reference: something else clearing the page for a fresh game (without going through leaveRemoteGame) can detach the old one from the document while this module's own reference to it lives on.

setCountdownDeadline()

Start, update, or stop the visual countdown, as each new deadline (or its absence) comes in.

stopCountdown()

Remove the countdown entirely - called once the remote game is left.

renderInto(root, redactedBody)

Replace the root's own body content with a freshly-inflated redacted body's children, rendered directly as the body's own children - not nested one level deeper under some other wrapper - so a path captured from this tree (getItemPath) and one resolved against the server's own root (getItemByPath) agree: both are root -> body -> [boards, placement panel], the exact shape redactGameBody sends.

stopForwarding()

Stop forwarding (and the countdown, which can't be running once the game has ended anyway) without touching whatever is currently rendered - used once the game is actually over, so the final score screen's own Play Again button (already rendered by the same push that triggered this) resolves to a real local listener on its next click instead of being forwarded into a game that no longer exists.

enterRemoteGame(root, firstUpdate)

Start rendering and interacting with a remote game, reusing the app's own existing root rather than a second one (a second documentDomItem() would claim the same real document.head/body the app's own root already has - exactly the collision the server's own per-room roots had to avoid, see server/gameplay.ts). Every click/change on the rendered tree is forwarded to the server instead of run locally (setForwardEvents) - the server's own receiveForwardedEvent resolves and dispatches it, and the resulting gameUpdate re-renders this same tree fresh. Leaving the lobby's own listeners (presetListener, remoteListener, ...) alone is safe because the lobby's own markup is no longer in the tree by the time this runs - clearChildren above already removed it.

Forwarding comes back off once the game actually ends (see isGameOver/stopForwarding) - the final-score screen's own Play Again button needs to run as a real local listener, not a forwarded one - and back on again for whatever the next gameUpdate turns out to be once Play Again succeeds and a fresh game starts.

leaveRemoteGame()

Stop forwarding and clear whatever the remote game last rendered, so the root can go back to running locally.

redactBoard(board, ownBoard)

A redacted clone of a board: every tile is kept (same count, same tree position - nothing is removed, so it stays renderable and clickable exactly like a local board), but hasShip is set to false on any tile that is neither hit nor on the viewer's own board. Which cells to redact is read from the real board, matching the rule the robot's own targeting already follows (see robot/densityTargets.ts's buildShotState), generalized from "what the AI may read" to "what a remote viewer may be sent" - only the hidden tiles' hasShip is mutated on the clone.

setViewShip (src/cells/setViewShip.ts) also bakes a grey backgroundColor directly onto a ship tile's own attributes.style the moment it is placed - meant for local play, where seeing your own ship as you place it is the point. That baked style survives this clone untouched unless cleared here too, which would leak every unhit ship's exact position through the rendered colour even with hasShip correctly hidden - clearing it only for the same cells hasShip is cleared for keeps a legitimate hit's own red/white colouring (set afterwards by colourHitCell, both colours equally public) untouched.

redactShip(ship)

A ship, reduced to what is always public: its name, length and status - never its parts' positions. parts is kept as an array of the right length (playerStats reads parts.length), but its entries are placeholders - a ship's parts are the same Tile objects the board holds, so leaving them as-is on a redacted clone would leak exact ship position through this second path even with the board's own tiles correctly redacted.

classNameOf()

The class name of an item, if it has one - the same lightweight check used throughout this file.

disableControls(panel)

A clone of a panel with every one of its own controls (anything but its message, which is never secret - ship sizes are public fleet knowledge, only position is hidden) disabled - used for a remote placement/ordering panel that is not this viewer's own to interact with. Builds a new object rather than mutating the given one, since it is already part of an outer clone (redactPlayer/redactGameBody) that must stay independent of the real tree.

hidePlayerControls(stats)

A clone of a player's own stats panel with its player-controls entirely removed - used for anyone else's own panel, as rendered for a viewer who is not that player. The only control remote play ever puts there is the hint checkbox (the own-ships checkbox is solo-mode only, never present in a multiplayer game - see playerStats.ts), which toggles THAT player's own showHint preference: hintListener.ts resolves whichever player owns the clicked element, regardless of who actually clicked it, so left as-is a viewer could flip another player's own hint setting from their own screen, and would see a checkbox that is really none of their business in the first place. Name and ship list are never secret, so nothing else here needs touching.

redactPlayer(player, viewer)

One player, redacted for a given viewer: a clone of the real player, with its board and fleet redacted per redactBoard/redactShip, its own placement panel disabled, and its own hint checkbox removed entirely, all unless this is that player's own viewer. Everything else (name, colour, robot/human, overall status, whose turn it is) passes through unchanged. children is kept in step with whichever of its own entries changed, by index, rather than assumed to always be exactly [turn-badge, board, stats] - a remote game's own fourth child (its placement panel) would otherwise silently be dropped.

redactGameState(players, viewer)

The whole game, redacted for one viewer: every player, each with their own board redacted according to whether viewer owns it. This is what is safe to send to a remote client for viewer's own connection - it contains nothing about any board's hidden ship positions except the viewer's own, and - unlike a flat data snapshot - it is still a real, renderable, clickable DomItem tree: a remote client can inflate and render it with the exact same components local play already uses, and forward its clicks the same way.

applyPlacingView(players, redactedPlayers, viewer)

Reduces the boards wrapper to just viewer's own player while placement is actively running - nobody else's board or placement panel is sent at all, not just hidden with CSS. A first version of this function did exactly that (hide via a placing-hidden class, every player still present, same shape as the server's own tree), because trimming the array broke json-dom's own position-based forwarding at the time (setForwardEvents/getItemByPath resolved a click by the index a tile sat at in the tree, which a joiner's own trimmed view - always player 0 to themselves, never to the server - did not match: "remote player was unable to click or update anything during placement", a real reported bug, not a guess). Forwarding resolves by a stable id now instead (see json-dom's getItemById, JSON-DOM#95), which does not care whether the two trees are even the same shape, so the actual data-minimizing version is safe again - another player's own placement never needs to reach a viewer's screen at all, closing the devtools-visible gap PR #132's server-side ownership check already defends against independently (this is "also don't send it", not a replacement for "also don't trust it").

applyPlayerView(players, redactedPlayers, viewer)

Marks each already-redacted player with which role they play in viewer's own current view (see playerView.ts's roleFor) - only ever adds a class, never touches the board/fleet data itself, which is already correctly redacted either way. The new client-side CSS this enables is what actually turns a summary role plainer (no highlighting, no animation) - nothing here removes any content.

Takes the real (pre-redaction) players alongside their already-redacted clones, in the same order, rather than deciding each role from the redacted list alone: redactPlayer's own siFunciona.cloneObject means a redacted entry is never === viewer any more, even for viewer's own - roleFor's identity check needs the real reference to ever resolve 'own' correctly.

redactGameBody(body, players, viewer)

A whole screen's worth of game state, redacted for one viewer - the boards wrapper's own children replaced with redactGameState's result, and (for a remote game) the global ordering panel's own Random/Set order controls disabled for anyone but the host - players[0] is always the room's host for as long as any game of theirs is running (the host is always the first to join a room, and the whole room closes if they ever leave, so this holds without needing to thread a separate host id through here). The final-score screen's own Play Again button is left exactly as sent - any player may click it, since all it ever does for its own clicker is show the room's own waiting room again, nothing that needs a host check. Everything else (the robots-only show-all-ships control, if present) is kept as is, since none of it carries anything secret. This is what a remote client actually renders and interacts with: the exact same markup local play already uses, inflated from this instead of built fresh.

While placement is actively running (remotePlacementStage(body) === 'placing'), only viewer's own player is sent at all (see applyPlacingView) - nobody else's board, panel or deadline reaches this viewer's screen during placement. Every other stage (choosing/ordering/shuffling/chosen) still includes and shows everyone, same as today - the host needs to see and click every player's board to set the order. Once placement is over entirely (stage is null), applyPlayerView marks each player's role for this viewer, so the client's own CSS can style a summary player plainer.

connectLobbySocket(url)

The lobby socket, connecting on first use. A test can connect it to its own ephemeral server before triggering any UI action, by calling this directly with that server's URL - the UI's own calls below then reuse that connection.

disconnectLobbySocket()

Close the lobby socket and forget it, so the next connectLobbySocket call starts fresh.

createRoom()

Create a room as its host, resolving with the room's state once the server acknowledges it, or an error.

peekRoom()

Look up a room's own lobby name and host's name by its code, without joining it - lets a joiner see whose game it is before they commit their own name (see mainMenu.ts's remote-join-confirm).

joinRoom()

Join an existing room by its code, resolving with the room's state, or an error if it could not be joined.

onRoomUpdate()

Be told whenever the room's state changes (a player joins or leaves). Replaces any previous listener rather than adding another - Play Again (see remotePlayAgainListener.ts) re-enters the same waiting room, which would otherwise register a fresh listener each time, piling up duplicate renders on every room update.

onRoomClosed()

Be told if the host leaves, closing the room for everyone still in it. Replaces any previous listener, for the same reason as onRoomUpdate above.

getSocketId()

This connection's own socket id, once connected - used to tell whether this player is the room's host.

startGame()

The host starts the game: every connected player becomes a human player, in the room's own join order, and robots (if any) fill whatever seats are still empty - the server validates this against the room's actual remaining capacity.

onGameUpdate()

Be told whenever the game's state changes - the redacted view of the whole screen, for this connection alone. Replaces any previous listener rather than adding another - a rematch (see remotePlayAgainListener.ts, server/lobbyServer.ts's relaxed startGame guard) re-enters a second real game on the same connection, which would otherwise leave the first game's own render loop listening alongside the new one, forever.

sendGameAction()

Forward a user interaction to the server instead of running its listener locally - see setForwardEvents.

waterTile()

Set the style for tiles representing water: a default (unhit, shipless) tile with an empty point, ready to be given its real point when the board is built.

shipTile()

Set status and custom properties for tiles that have a ship

ship(name)

Store properties of a ship which includes an array of all associated ship tiles.

playerStats(player, status, item)

The defined attributes for each player. The name and status run across the top; the player's checkboxes are stacked on the left, and the ship list is on the right.

playerSet(board, name)

Store the player attributes. board and shipFleet start as placeholders (an empty object, an empty array); they are given their real values once the board is built (see buildPlayers).

hitTile()

Set the status of the tile to hit.

gameTile()

Default properties for a tile in the battleship game.

showShipsControl()

The one control for the robots-only game: a single checkbox to show every ship on every board, rather than one per board. It sits above the boards.

remotePlacementPanel()

Each player's own placement controls, rendered as part of their own subtree (not one shared panel at the body level, like local hot-seat's placementPanel) - so every connected player places their own ships on their own board whenever they want, with no "look away" handoff. Redacted per viewer (see redactGameState.ts): only the owning player's own copy is ever enabled - everyone else's is always disabled, showing only ready/not-ready.

remoteOrderingPanel()

How the turn order is decided once every player has placed: the host picks Random or Set order (clicking each player's board in turn); everyone else sees the exact same status message, with no controls of their own. Enforced server-side (see server/lobbyServer.ts's gameAction handler, which rejects a non-host's action during any stage but placing), not just by these buttons being disabled on a non-host's own redacted copy.

remoteFinalScore(players)

The final score screen for a remote room's game: the same public score cards local hot-seat shows (see finalScore.ts), plus Play Again (see remotePlayAgainListener.ts) and Leave (see remoteLeaveListener.ts) - both open to every player, neither destructive, so there is no need to restrict who can press either one. Local hot-seat's own separate "Change Settings" button has no remote equivalent: Play Again already returns everyone to the waiting room, where the host can change the hint setting before starting again, so a second button offering the same trip would be redundant.

placementPanel(message)

The panel shown during the placement phase: a message for whoever is placing, and their buttons. Each button has one class name, which is how the layer finds it and how one listener tells them apart (see placementListener).

mainMenu()

The entry screen. It shows the game types (presets) first. Choosing one reveals the lobby, which is the form for the game: how many humans and robots, the hint setting, and who goes first. Start in the lobby submits the form, as before.

finalScore(players)

Display the final scores after a game has ended, with three ways to go on: Play Again (same settings, places ships again), Change Settings (back to the lobby, pre-filled with this game's settings), and Main Menu (back to choosing the game type).

boards(players)

Wrapper div for player data / boards

update3dCell(config, matrix, x, y, z, isRobot)

Given a cell and new config data, update the data of the cell

shadeShips(player, shown)

Show or hide a player's ships on their board. Ships which have been hit keep the colour they were given when hit, so only the parts not yet hit change.

setShip(matrix, point, view)

Set a specified point to be part of a ship

configureHtml(config, isRobot)

Update view based on actions performed

colourHitCell(config)

Colour a cell once it has been hit: red for a ship, white for water. Cells only have a style object once something has resized or highlighted them, so it is created here when it is missing.

markBoard(victim, attackable)

Mark the cells of a board which can still be attacked: the ones not yet hit. The class is what the stylesheet uses to show a target cursor and a highlight when hovered. Tiles keep their own class ('column'), so only this is added.

showValidTargets(victim)

Show a human which cells of the board they are attacking can still be hit.

clearValidTargets(victim)

Remove the markers when the turn passes.

updateScore(hitShip, sunkShip, players)

Update all game stats after each player round

updatePlayerStats(player, status)
updatePlayer(player, hitShip, sunkShip)

Track player stats such as attacks and turns

shipsListener(e, target)

The ship toggles. A player's own checkbox (one-player game) shows or hides their ships. The all-ships control (robots-only game) does the same for every board at once.

randomAttack(attacker, players)

Attack a random still-unhit cell on a random still-afloat opponent's board - no targeting logic at all, unlike computerAttack.ts's own density-based choice (this is deliberately the dumbest possible fallback, not a robot's turn). Used when a human player's own turn-timeout deadline expires in a remote game (see server/gameplay.ts) instead of leaving everyone else waiting on them indefinitely.

roleFor(player, viewer)

Which role player plays in viewer's own current view: their own board, a board they can actually attack right now, or everyone else (not their turn, or already eliminated) - still a real board, just styled plainer, no highlighting or animation (see redactGameState.ts's applyPlayerView, which is the one place this actually changes anything sent or rendered). Independent of local vs remote - whatever renders a player's own screen, remote today, local single-player later, decides what to draw for each role; this only decides which role applies.

applyLocalPlayerView(players, viewer)

Marks every player with which role they play in viewer's own current view (see playerView.ts's roleFor) - the local-play counterpart to redactGameState.ts's applyPlayerView, which does the same thing for a remote viewer's own redacted clone. Local play never redacts data, so this mutates the real tree directly, the same way updatePlayer.ts already mutates a player's own outline/box-shadow style. Only ever changes a className, never touches board/fleet data.

viewer === null means nobody is "own" (robots-only/watch - there is no human playing) - every player gets role-summary unconditionally.

hintListener(e, target)

The hint checkbox in a human's panel: switching it on shows the heat map at once if it is their turn.

victimsOf(player)

The boards a player is attacking: every other player's board.

showHeatHint(victim)

Shade a board's cells by the robot's weighting, as a hint to a human about where a ship may be. The weighting only uses what a player can already see: attacked cells, hit parts and the lengths of unsunk ships.

clearHeatHint(victim)

Put a board's cells back to their normal border.

getNextAttacker(attacker, players, hitShip)

Based on the current attacker and list of players, return the next attacker.

findNextAttacker(attacker, players, attackerIndex)
endGame(winner)

Final state once a game is won (only one player remains). Local hot-seat's own finalScore screen (Play Again, Change Settings, Main Menu) assumes one physical screen controlling the whole game - a remote room's own game session overrides this via onGameOver (see gameSession.ts, server/gameplay.ts) with something that makes sense for several independent, already-forwarding clients instead.

getAttackLock(item)

Whether attacks are being ignored right now for the given item's game: the board is locked while the turn changes over. Shared by the code which starts and ends a turn and the code which takes an attack. Each game has its own lock (see gameSession), so one game's turn change never blocks another's.

attackListener(e, target)

target is the board the listener was attached to (see buildPlayers): a DomItem here, like every listener's target, but really always a Board. During the placement phase a click places a ship rather than attacking - local hot-seat's own handoff-based placement, or (for a remote game) simultaneous per-player placement/ordering; exactly one of the two is ever active for a given game, never both.

attackFleet(target)

Perform attack on an enemy board / cell

hasTrait

The typed version of json-dom's hasTrait, it narrows an item to the trait it was checked for.

Kind: global variable

PLACEMENT_TIMEOUT_MS

How long players have to finish placing before any still-pending ships are placed at random for them.

Kind: global variable

lastRoomState

The room's own last known state (its code, name, host, and players) - kept up to date by renderRoomState below regardless of whether the waiting room is currently shown, so Play Again (see remotePlayAgainListener.ts) can show it again for everyone once a game ends, without a fresh server round trip to ask for it again.

Kind: global variable

pendingJoinCode

The room code a successful peek (see peekAndConfirm) found - remembered so the Join confirm panel's own submit can join it without needing its own room-code field.

Kind: global variable

playerColours

The colour each player is identified by, in the order they are created. They are bright enough to read on the dark background, and distinct from one another.

Kind: global variable

defaultShipSpecs

Create a default fleet using the standard battleship lengths.

Kind: global variable

Param
matrix
view

STAGE_MS

How long each stage of the robot's attack is shown for, in turn.

Kind: global variable

DAMAGED_WEIGHT

Extra weight for placements which explain a ship that is already damaged, relative to a fresh ship.

Kind: global variable

ADJACENT_WEIGHT

Extra weight for an unattacked cell next to a hit on a ship which is not yet sunk, the partial-hit follow-up.

Kind: global variable

DEFAULT_URL

No server is deployed yet - this only works against a locally-run lobby server (npm run dev:lobby).

Kind: global constant

setViewShip

Set a visible ship part at the given coordinates (shown with a grey background).

Kind: global constant

setHit

Mark the cell at the given coordinates as hit.

Kind: global constant

setHiddenShip

Set a hidden ship part at the given coordinates (not shown, the default cell styling still applies).

Kind: global constant

ATTACKER_LOCK_MS

How long a human attacker's own lock stays on once their turn starts. This used to be 400ms to match a board-resize animation (sass/game-pieces.sass's old per-turn .matrix resize, confirmed via git history - d1be1695); that resize was removed later and nothing on .player itself has ever had a CSS transition to wait out, so the original value outlived the thing it was timed to. It stays non-zero, just much shorter, for the one real job it still does in local hot-seat: attackFleet.ts's guard only blocks attacking the current attacker's own board, so without this lock a human could rapid-click several different victims inside what should be a single turn. Remote play already blocks the same thing independently, by socket identity (lobbyServer.ts's gameAction handler), so this is redundant there - but it costs nothing to keep uniform.

Kind: global constant

queueTimeout(item, fn, time, ...args)

The timed queue the given item's game runs on: steps queued here run one after another, after their delay. Each game has its own queue (see gameSession), so two games' turn changes, robot attacks, and animations never interleave.

Kind: global function

Param Default
item
fn
time 0
...args

getLowStatusItems(items)

Given an array of items, return all items which have the lowest status property

Kind: global function

Param
items

getBrokenShipsPlayers(players)

Return all of the players which have broken ships.

Kind: global function

Param
players

getBrokenItems(items)

Given an array of items, return all of the items which have a status less than 100, but more than 0

Kind: global function

Param
items

getAllNonHitCells(matrix)

Get all points which were not yet hit in the matrix.

Kind: global function

Param
matrix

getALowStatusItem(items)

Given an array of items, return the item with the lowest status property (at the end of the array)

Kind: global function

Param
items

filterAdjacentPoints(pnt)

Used to generate 'checkerboard' style attack by only attacking every non-edge-touching cell

Kind: global function

Param
pnt

checkIfShipCell(pnt, matrix)

Return the hasShip tile boolean at the specified point.

Kind: global function

Param
pnt
matrix

checkIfHitCell(pnt, matrix)

Return the isHit tile boolean at the specified point.

Kind: global function

Param
pnt
matrix

clearBody(parent)

Remove everything from the page: whatever screen was showing (the menu, a finished game's final scores, or a game in progress), so a new one can be built on a blank page.

Kind: global function

Param
parent

applyInitialLocalView(players, mode)

Set every player's role for robots-only/watch, the one mode whose view never changes by turn at all - nobody is ever "own", so there is nothing for updatePlayer.ts's own per-turn hook to recompute (see its own comment). Solo's "own" viewer is also fixed (always the human) but its opponents' target/summary split still depends on whose turn it actually is (robots attack each other too, not just the human), so solo is recomputed on every turn the same way hot-seat is - see updatePlayer.ts - not set up here. Remote's own per-socket roles are applied separately, per viewer, by redactGameState.ts - never here, on the one shared server tree.

Kind: global function

Param
players
mode

startRound(order, firstGoesFirst)

Pick the first attacker, and let a robot start if it is one.

Kind: global function

Param Description
order
firstGoesFirst undefined when the order was chosen, so the first player of it goes first

startNewGame(parent, humans, robots, firstGoesFirst, hints, placementStarter, onPlayersBuilt)

Build the players, place their ships, and start the round. Shared by beginRound (reading these settings from the lobby form) and playAgain (reading them from the settings the last game was started with) - either way, this is the one place a round actually begins. A remote game passes startRemotePlacement (simultaneous per-player placement, no handoff) in place of the default, and onPlayersBuilt to set each player's real name before anything is ever rendered or pushed - see server/gameplay.ts. Local play needs neither: a human types their own name during their own handoff screen (see placement.ts's nameInput), since there is nothing to know upfront.

Kind: global function

Param
parent
humans
robots
firstGoesFirst
hints
placementStarter
onPlayersBuilt

startMenu(parent)

The entry function

Kind: global function

Param
parent

update(item, attributes)

Update an item's attributes in place on the real element.

Kind: global function

Param
item
attributes

show(item, shown)

Show or hide an item by its display style.

Kind: global function

Param
item
shown

showLobby(menu, preset, values)

Show the lobby for a game type, inside an already-rendered main menu: sets its title and field limits from the preset, fills in the given values (or the preset's own defaults), and reveals it in place of the game types.

Kind: global function

Param
menu
preset
values

returnToLobby(e, button)

Back to the lobby, with the settings from the game that just ended already filled in, so they can be changed before playing again - unlike the main menu button, which goes all the way back to choosing the game type.

Kind: global function

Param
e
button

restart(e, button)

Kind: global function

Param
e
button

remotePlayAgainListener(e, target)

The final-score screen's Play Again button, for a remote game - any player can click it (there is nothing destructive about asking to see the waiting room again), not just the host. Leaves the finished game (stopping forwarding, clearing the rendered tree - see remoteGame.ts's leaveRemoteGame) and shows the same room's own waiting room again, with the same players and the same host, using the room's own last known state (see remoteListener.ts's getLastRoomState - kept current throughout, not just while the waiting room is visible, so no fresh server round trip is needed here). From there, the host starts a new round exactly the way they started the first one - server/lobbyServer.ts's startGame handler already allows a room's own game to restart once it has actually ended.

This only ever runs client-side, with forwarding already turned off for this exact reason (see remoteGame.ts's enterRemoteGame) - the server's own copy of this listener name is a trivial stand-in, never meant to actually run.

Kind: global function

Param
e
target

remotePlacementListener(e, target)

The remote placement/ordering panels' own buttons - each player's Randomise/Ready, and the host-only Random/ Set order. Told apart by class name, same pattern as local placement's own placementListener.

Kind: global function

Param
e
target

isRemoteSessionActive()

Whether a remote placement/ordering phase is running for this item's game - so a board click during it is routed here instead of to a normal attack (see attackListener.ts).

Kind: global function

cancelRemotePlacement()

Cancel this game's own placement/ordering session, if one is still running, clearing its pending auto-finish timer and forgetting it. Without this, a room abandoned (or ended some other way, e.g. onGameOver - see server/gameplay.ts) while still placing/ordering leaves its own 60-second timer pending; unref() keeps that alone from holding the process open, but it can still fire later against an already-torn-down root, crashing on whatever stale assumption autoFinishPlacement/beginOrdering makes about its shape. Safe to call even when no session is running (normal completion already deletes its own via finishOrdering).

Kind: global function

isHostOnlyStage()

Whether only the host may act right now - every stage except placing itself (see server/lobbyServer.ts).

Kind: global function

remotePlacementStage()

The current remote placement/ordering stage for this item's game, or null once the round has actually started (no session is running any more - see finishOrdering, which deletes it). redactGameState.ts's own redactGameBody reads this to decide whether a viewer's own push should include every player (ordering, where the host needs to see and click everyone) or only their own (placing itself, see the plan this served - every other player's board/fleet/placement panel never need to reach a still-placing viewer).

Kind: global function

renderPanel()

Refresh one player's own placement panel to match their current state.

Kind: global function

showStart()

Mark which cells a ship-in-progress could end on, same visual as local placement's own showStart.

Kind: global function

startRemotePlacement(players, body, done, onTimerChange)

Begin a remote game's placement phase: every human places their own ships on their own board at the same time - no handoff, no "look away". done runs once the order is set, with the order play will take. onTimerChange runs whenever the deadline below actually fires and changes state on its own - every other state change in this module happens inside a function a dispatched/forwarded click calls directly, which the server's own broadcast-after-dispatch already covers; the deadline is the one change that happens on a raw timer with nothing else watching for it, so without this hook a client that lets it expire sees nothing at all, even though the server's own state has already moved on. A robot filling an empty remote seat (see server/gameplay.ts's startRoomGame) already has its fleet placed by buildPlayers.ts, same as local play's own robots - it is seeded ready here immediately, never waiting on a ready-up that nothing would ever drive, and never gets a placement panel of its own.

Kind: global function

Param
players
body
done
onTimerChange

placeRemoteShip(tile, board)

A click on a player's own board during placement: the first click sets where a ship starts, the second where it ends (an invalid second click is refused and the start is forgotten) - exactly local placement's own two-click mechanic, just resolved from the clicked board's own owner instead of a shared "current player".

Kind: global function

Param
tile
board

pickOrderPlayer()

A click on a player's own board while the order is being set (see beginOrderSet) - adds them to the order.

Kind: global function

handleRemoteBoardClick()

A board click while a remote placement/ordering phase is running - routed by the current stage.

Kind: global function

beginOrdering()

Once every player is ready (or the timer below fires), hide their panels and show the ordering choice.

Kind: global function

autoFinishPlacement()

Any player still not ready when the deadline passes has their remaining ships placed for them at random.

Kind: global function

readyRemotePlayer()

The Ready button: locks a player's own fleet in once nothing is left pending.

Kind: global function

randomiseRemoteShips()

The Randomise button: re-rolls just this player's own remaining fleet, same as local placement's own version.

Kind: global function

beginOrderSet()

The Set order button: from here, clicking each player's board in turn (see pickOrderPlayer) sets the order.

Kind: global function

highlightOnly()

Outline one player's panel, and clear the outline from the others - the random order's own shuffle animation.

Kind: global function

shuffle()

A shuffled copy of the players, in a random order - identical to local placement's own version.

Kind: global function

chooseOrderRandom()

The Random button: a short highlight passes over the players, then lands on a full random order.

Kind: global function

finishOrdering()

The order is set (either way) - show it briefly, then begin the round.

Kind: global function

getLastRoomState()

The current room's own last known state, if this connection has ever been in one.

Kind: global function

showSubPanel()

Show exactly one of remote-entry's own four sub-panels (see mainMenu.ts), hiding the other three.

Kind: global function

peekAndConfirm()

Look up a room by code (without joining it - see lobbySocket.ts's peekRoom) and move to the Join confirm panel showing whose game it is, or fall back to the code-entry panel with the error shown inline if it could not be found. The code-entry panel (with the code already filled in) shows immediately, before the lookup even resolves, so a direct link lands on something straight away rather than a blank wait.

Kind: global function

showRemoteEntry()

Show the remote entry area in place of the game-type tiles - either the Host/Join choice (no code yet, the Online Multiplayer tile itself), or (a shared join link - see main.ts) straight to the Join confirm panel once that code's own room has been found.

Kind: global function

renderRoomState()

Replace the waiting room's player list, lobby name, room code and host controls with a freshly-received room state.

Kind: global function

leaveToPresets()

Leave whatever room is open and show the game types again, clearing any status message - or, with a message (the room closed on its own), show it on the Host/Join choice panel instead of the tiles.

Kind: global function

enterWaitingRoom()

Once a room is created or joined (or a finished game's Play Again brings everyone back to it - see remotePlayAgainListener.ts), watch it for changes and show the waiting room. Puts the room's own code in the address bar too, so the host (or anyone else) can just copy the current URL to share a join link - see showRemoteEntry, which reads it back out on the receiving end.

Kind: global function

remoteListener(e, target)

The Online Multiplayer tile, its Host/Join choice and forms, and the waiting room it leads to. Room/presence only - actual gameplay over the socket is a separate, later piece.

Kind: global function

Param
e
target

remoteLeaveListener(e, target)

The final-score screen's Leave button, for a remote game - any player can click it, same as Play Again (there is nothing destructive about leaving). Unlike Play Again, this takes just its own clicker all the way back to the main menu: leaves the finished game (stopping forwarding, clearing the rendered tree - see remoteGame.ts's leaveRemoteGame), disconnects this connection's own lobby socket and clears the shared ?room= link from the address bar (the same cleanup remoteListener.ts's own leaveToPresets does for the waiting room's Leave button), and shows a freshly rendered menu - whose default screen is the game-type tiles, not the waiting room.

This only ever runs client-side, with forwarding already turned off for this exact reason (see remoteGame.ts's enterRemoteGame) - the server's own copy of this listener name is a trivial stand-in, never meant to actually run (see server/gameplay.ts's buildIsolatedRoot).

Kind: global function

Param
e
target

presetListener(e, target)

The game types on the entry screen. Each reveals the lobby for that type (see showLobby). Back hides the lobby again. Presets (and Back) are told apart by their class names, so one listener handles all of them.

Kind: global function

Param
e
target

playerColour(index)

The colour for the player at this position in the game.

Kind: global function

Param
index

playAgain(e, button)

Play again with the same settings as the game that just ended: the same humans, robots, who goes first, and hint setting. Only the settings carry over, not the fleets or the board state - a new game still places its ships, for multiplayer same as the first time.

Kind: global function

Param
e
button

placementListener(e, target)

The placement buttons: Continue (after the handoff), Randomise, and Done. They are told apart by class name.

Kind: global function

Param
e
target

setBadge(player, text)

The label above a player's board, which shows their place in the order.

Kind: global function

Param
player
text

nameInput()

The name field on the placement panel.

Kind: global function

readName(session, player)

Save the name typed for a player, if one was typed. The default name (Player N) is kept otherwise.

Kind: global function

Param
session
player

isPlacing()

Whether a placement phase is running for this item's game, so board clicks are placements rather than attacks.

Kind: global function

startPlacement(players, body, done)

Show the panel and start with the first human's handoff. done runs once the round is ready to start, with the order the players will take turns in. With several players that order is chosen here; otherwise it is the seat order.

Kind: global function

Param
players
body
done

setStatsShown(players, shown)

Show or hide every player's stats (health, and the hint checkbox), which are not wanted during placement.

Kind: global function

Param
players
shown

showOnly(session, player)

Show only this player's board, or with null hide every board.

Kind: global function

Param
session
player

showAll()

Show every board, so the players can see and click each other's.

Kind: global function

continueTurn(item)

Continue: from a handoff it starts that player's placement; from the ready screen it starts the round.

Kind: global function

Param
item

showStart(session, point, size)

Show where a ship has started, and the cells it could end on: every cell in a straight line from the start which would be a valid placement. Clicking the start again, or any other cell which is not valid, cancels the start. With no start (null) every mark is removed.

Kind: global function

Param
session
point
size

endCount(session, start, size)

The number of cells a ship of this size could end on from the start, so the message can say when there are none.

Kind: global function

Param
session
start
size

placeCell(tile, board)

A click on a board: during placement, the first click sets where a ship starts and the second where it ends (an invalid second click is refused and the start is forgotten). While the order is being set, a click picks that player.

Kind: global function

Param Description
tile
board the board that was clicked

randomise(item)

Clear the current player's board, then place their whole fleet at random.

Kind: global function

Param
item

hideShips(player)

Hide a player's ships again: during placement they are shaded so the player can see them, and in play they must not be.

Kind: global function

Param
player

showReady(session)

Every human has placed. With several players, the boards are shown again and they choose who goes first. With one human, or robots only, the boards are hidden and everyone is asked to confirm. The ships are cleared while no board is showing, so they cannot be seen fading out.

Kind: global function

Param
session

chooseOrder(item)

Set the order by clicking the boards: each click adds that player to the end of the order.

Kind: global function

Param
item

ordinal(place)

The word for a place in the order: 1st, 2nd, 3rd, then 4th and so on.

Kind: global function

Param
place

pickPlayer(session, board)

Add a clicked board's player to the end of the order, and finish once everyone is in it.

Kind: global function

Param
session
board

showOrder(session)

Show the order which has been set, and wait for Continue to start.

Kind: global function

Param
session

randomOrder(item)

Random: a short highlight passes over the players, then lands on a full random order.

Kind: global function

Param
item

shuffle(players)

A shuffled copy of the players, in a random order.

Kind: global function

Param
players

highlightOnly(session, chosen)

Outline one player's panel, and clear the outline from the others.

Kind: global function

Param
session
chosen

reorderBoards(order)

Put the boards in turn order, so the page reads in the order play will go in, and turns follow it. The game's tree and the page are both reordered: moving a panel with json-dom would detach it from the page.

Kind: global function

Param
order

startRound(session, order)

The round starts: every board and its stats are shown in turn order, and the placement panel goes.

Kind: global function

Param
session
order

finishTurn(item)

The player is happy with their fleet: the next human places, or, after the last, everyone is asked to confirm.

Kind: global function

Param
item

isValidPlacement(board, start, end, length)

Whether a ship of this length can go from start to end: a straight horizontal or vertical line of exactly that many cells, inside the board, and not touching a ship which is already there.

Kind: global function

Param
board
start
end
length

placeShip(board, shipInfo, start, end, view)

Place a ship from the player's chosen start and end points, if the placement is valid. Returns false if it is not.

Kind: global function

Param Default
board
shipInfo
start
end
view true

validPlacements(matrix, shipLength)

Every straight, horizontal or vertical, start and end point of a ship of this length which fits on the board without touching a ship already there. Ships only go along one axis for now (no diagonals, z is always 0) - matrix-dom's getLinesOfLength enumerates every line the geometry allows, and only knows about shape, not what occupies a cell, so the blocked-cell filter (and any future direction, if diagonal or 3D ships ever arrive) stays here.

Kind: global function

Param
matrix
shipLength

generateStartEnd(matrix, shipLength)

Pick a start and end point for a ship of the given length, at random from every placement that fits. Throws if no placement fits, rather than searching forever.

Kind: global function

Param
matrix
shipLength

generateRandomFleet(ships, matrix, view)

Create a series of randomly placed ships based on the provided shipLengths. The optional parameter view will set the visibility of the ships.

Kind: global function

Param Default
ships
matrix
view false

getSession(item)

The session for whichever game item belongs to - any DomItem in that game's tree, or its root itself, works the same way. A game gets its own session the first time anything asks for it, and it is garbage-collected along with its root once nothing else references the game any more - there is nothing to explicitly tear down.

Kind: global function

Param
item

presetForMode(mode)

The preset for a given game mode, so the lobby can be shown for it without a preset button having been clicked.

Kind: global function

Param
mode

buildShip(shipInfo, line, matrix, view)

Generate a ship with the provided line of points. The visibility of the ship on the board is determined by the view parameter.

Kind: global function

Param Default
shipInfo
line
matrix
view false

buildPlayers(humans, root, robots, players)

Create players and associated properties. Takes an integer for the number of players to generate. Returns an array of players. WARNING: This is a recursive function.

Kind: global function

Param Default Description
humans
root the game's own root, since a player isn't attached to it yet at this point - see playerStats
robots 0
players

beginRound(e, mainForm)

Logic for setting up and starting a new round from the lobby form.

Kind: global function

Param
e
mainForm

remainingHitPoints(player)

The hits still needed to sink a player's unsunk ships: the unhit parts of every ship which is not yet sunk.

Kind: global function

Param
player

best(players, score, highest)

The players with the lowest score, or with the highest when highest is set.

Kind: global function

Param Default
players
score
highest false

afloat(players)

The players still afloat, or everyone when none is.

Kind: global function

Param
players

eliminationRule(players)

Elimination first: attack the player with the fewest hits still needed to sink everything they have left, so the robot knocks them out soonest. Ties are picked at random. This is the rule the game uses.

Kind: global function

Param
players

hitChanceRule(players)

Hit chance first: attack the player whose best cell has the highest chance of holding a ship part. Not used by the game yet. In simulation it prolongs games, because it favours boards with more ship left, but a game style which scores hits may want it.

Kind: global function

Param
players

selectTargetPlayer(players, rule)

Choose which player to attack, using the rule for the game style (elimination for now).

Kind: global function

Param
players
rule

selectTargetCoordinate(victim)

Choose which coordinate to attack, in layers: the density model first, then the checkerboard over every unattacked cell. The density scores are shown as a heat map, so the robot's thinking can be seen before the checkerboard narrows the choice.

Kind: global function

Param
victim

shade(intensity)

A faint yellow for the weakest cells up to a solid one for the strongest, so the spread of the robot's thinking shows.

Kind: global function

Param
intensity

paint(victim, point, color)

Kind: global function

Param
victim
point
color

resetTargets(data)

Kind: global function

Param
data

outlineBoard(player, color)

Colour every row of a player's board, which is how a whole board is outlined.

Kind: global function

Param
player
color

displayTargets(cells, target, victim, opponents)

The stages of the robot's thinking, shown in turn: the boards it is choosing between, the chosen board, the cells it weighs, then the cell it picks at random. The shot itself comes after the last stage (see computerAttack).

Kind: global function

Param
cells
target
victim
opponents

clearTargets(victim, opponents)

Take the display away once the robot has chosen: the heat map and every board outline.

Kind: global function

Param
victim
opponents

buildShotState(victim)

Build what the robot is allowed to know about a victim: which cells were attacked, which of those were hits (the hit parts of each ship, not the position of any part still unhit), and how many parts each unsunk ship has. Hit or miss is read from the ship parts' isHit flags, never from a tile's hasShip.

Kind: global function

Param
victim

refineTies(cells)

Among cells tied at the top score, prefer the checkerboard pattern used to find ships quickly. The partial-hit follow-up is not handled here: it is extra weight inside scoreTargets, so it is already part of the score.

Kind: global function

Param
cells

densityChoices(victim)

The density model's picture of the board: every unattacked cell with a score, shaded by how far it is from the top score (heat, for display), and the cells the robot chooses from (targets: the highest, narrowed to the checkerboard among them). targets is never empty while a ship remains.

Kind: global function

Param
victim

densityTargets(victim)

The attack points the density model considers most likely to hold a ship part, or an empty array when there is none.

Kind: global function

Param
victim

bestHitChance(victim)

The best chance that a shot at this player's board hits a ship: the highest per-cell hit chance.

Kind: global function

Param
victim

consistent(placement, shipHitKeys, missKeys, hitKeys)

Whether a placement could be where a ship really is: it avoids misses, covers all of the ship's own known hits, and touches no other ship's hit.

Kind: global function

Param
placement
shipHitKeys
missKeys
hitKeys

scoreTargets(state, damagedWeight, adjacentWeight)

Score every cell by how many ways the remaining ships could still cover it. Each ship contributes every placement consistent with what is known: it avoids misses, covers all of its own known hits, and touches no other ship's hit. Cells already attacked score zero. Unattacked cells next to a hit on an unsunk ship then get the adjacent weight on top.

Kind: global function

Param
state
damagedWeight
adjacentWeight

bestTargets(scores)

The unattacked cells which have the highest score. Empty when no score is above zero.

Kind: global function

Param
scores

hitChances(state, damagedWeight)

The chance that each unattacked cell holds a part of some remaining ship. Each ship's placements are weighted as in scoreTargets, then turned into a share of that ship's total, so a ship counts once however many placements it has. Not used by the game's robot yet: it is kept for game styles where the chance of a hit matters more than elimination.

Kind: global function

Param
state
damagedWeight

computerAttack(player, players)

Main AI logic for computer to attack, selects a target then performs attack function.

Kind: global function

Param
player
players

clearChildren()

Remove every one of a parent's children - the same pattern startNewGame's clearBody uses locally.

Kind: global function

deadlineOf()

Whichever deadline a pushed body actually carries right now, placement or turn - an empty string (the lingering, no-longer-relevant state left behind once that phase ends) is falsy, same as it being absent altogether, so both read the same way here.

Kind: global function

isGameOver()

Whether a pushed body is the final-score screen (see remoteFinalScore.ts) - the one point in a remote game's own lifecycle where forwarding has to come back off, so its Play Again button's click runs as a real local listener instead of being forwarded into a game that is already over.

Kind: global function

finalScoreOnly()

A pushed body, reduced to just its final-scores child - server/gameplay.ts's own onGameOver hook already clears everything else before a game-over push ever goes out, but rendering only ever what this module itself has confirmed is the final-score screen, regardless of whatever else might be sitting alongside it, means a future regression upstream (something left in the body that still references a listener name the real client only ever registers as a forwarder, like remotePlacementListener) clears the whole page with nothing on it, instead of just failing to show the one thing that is actually safe to show.

Kind: global function

ensureCountdownElement()

Create the countdown element if there is not already a live one in the page - not just a non-null reference: something else clearing the page for a fresh game (without going through leaveRemoteGame) can detach the old one from the document while this module's own reference to it lives on.

Kind: global function

setCountdownDeadline()

Start, update, or stop the visual countdown, as each new deadline (or its absence) comes in.

Kind: global function

stopCountdown()

Remove the countdown entirely - called once the remote game is left.

Kind: global function

renderInto(root, redactedBody)

Replace the root's own body content with a freshly-inflated redacted body's children, rendered directly as the body's own children - not nested one level deeper under some other wrapper - so a path captured from this tree (getItemPath) and one resolved against the server's own root (getItemByPath) agree: both are root -> body -> [boards, placement panel], the exact shape redactGameBody sends.

Kind: global function

Param
root
redactedBody

stopForwarding()

Stop forwarding (and the countdown, which can't be running once the game has ended anyway) without touching whatever is currently rendered - used once the game is actually over, so the final score screen's own Play Again button (already rendered by the same push that triggered this) resolves to a real local listener on its next click instead of being forwarded into a game that no longer exists.

Kind: global function

enterRemoteGame(root, firstUpdate)

Start rendering and interacting with a remote game, reusing the app's own existing root rather than a second one (a second documentDomItem() would claim the same real document.head/body the app's own root already has - exactly the collision the server's own per-room roots had to avoid, see server/gameplay.ts). Every click/change on the rendered tree is forwarded to the server instead of run locally (setForwardEvents) - the server's own receiveForwardedEvent resolves and dispatches it, and the resulting gameUpdate re-renders this same tree fresh. Leaving the lobby's own listeners (presetListener, remoteListener, ...) alone is safe because the lobby's own markup is no longer in the tree by the time this runs - clearChildren above already removed it.

Forwarding comes back off once the game actually ends (see isGameOver/stopForwarding) - the final-score screen's own Play Again button needs to run as a real local listener, not a forwarded one - and back on again for whatever the next gameUpdate turns out to be once Play Again succeeds and a fresh game starts.

Kind: global function

Param Description
root
firstUpdate the redacted body already received (the game has already started by the time this is called)

leaveRemoteGame()

Stop forwarding and clear whatever the remote game last rendered, so the root can go back to running locally.

Kind: global function

redactBoard(board, ownBoard)

A redacted clone of a board: every tile is kept (same count, same tree position - nothing is removed, so it stays renderable and clickable exactly like a local board), but hasShip is set to false on any tile that is neither hit nor on the viewer's own board. Which cells to redact is read from the real board, matching the rule the robot's own targeting already follows (see robot/densityTargets.ts's buildShotState), generalized from "what the AI may read" to "what a remote viewer may be sent" - only the hidden tiles' hasShip is mutated on the clone.

setViewShip (src/cells/setViewShip.ts) also bakes a grey backgroundColor directly onto a ship tile's own attributes.style the moment it is placed - meant for local play, where seeing your own ship as you place it is the point. That baked style survives this clone untouched unless cleared here too, which would leak every unhit ship's exact position through the rendered colour even with hasShip correctly hidden - clearing it only for the same cells hasShip is cleared for keeps a legitimate hit's own red/white colouring (set afterwards by colourHitCell, both colours equally public) untouched.

Kind: global function

Param Description
board
ownBoard whether the viewer this is being redacted for owns this board

redactShip(ship)

A ship, reduced to what is always public: its name, length and status - never its parts' positions. parts is kept as an array of the right length (playerStats reads parts.length), but its entries are placeholders - a ship's parts are the same Tile objects the board holds, so leaving them as-is on a redacted clone would leak exact ship position through this second path even with the board's own tiles correctly redacted.

Kind: global function

Param
ship

classNameOf()

The class name of an item, if it has one - the same lightweight check used throughout this file.

Kind: global function

disableControls(panel)

A clone of a panel with every one of its own controls (anything but its message, which is never secret - ship sizes are public fleet knowledge, only position is hidden) disabled - used for a remote placement/ordering panel that is not this viewer's own to interact with. Builds a new object rather than mutating the given one, since it is already part of an outer clone (redactPlayer/redactGameBody) that must stay independent of the real tree.

Kind: global function

Param
panel

hidePlayerControls(stats)

A clone of a player's own stats panel with its player-controls entirely removed - used for anyone else's own panel, as rendered for a viewer who is not that player. The only control remote play ever puts there is the hint checkbox (the own-ships checkbox is solo-mode only, never present in a multiplayer game - see playerStats.ts), which toggles THAT player's own showHint preference: hintListener.ts resolves whichever player owns the clicked element, regardless of who actually clicked it, so left as-is a viewer could flip another player's own hint setting from their own screen, and would see a checkbox that is really none of their business in the first place. Name and ship list are never secret, so nothing else here needs touching.

Kind: global function

Param
stats

redactPlayer(player, viewer)

One player, redacted for a given viewer: a clone of the real player, with its board and fleet redacted per redactBoard/redactShip, its own placement panel disabled, and its own hint checkbox removed entirely, all unless this is that player's own viewer. Everything else (name, colour, robot/human, overall status, whose turn it is) passes through unchanged. children is kept in step with whichever of its own entries changed, by index, rather than assumed to always be exactly [turn-badge, board, stats] - a remote game's own fourth child (its placement panel) would otherwise silently be dropped.

Kind: global function

Param
player
viewer

redactGameState(players, viewer)

The whole game, redacted for one viewer: every player, each with their own board redacted according to whether viewer owns it. This is what is safe to send to a remote client for viewer's own connection - it contains nothing about any board's hidden ship positions except the viewer's own, and - unlike a flat data snapshot - it is still a real, renderable, clickable DomItem tree: a remote client can inflate and render it with the exact same components local play already uses, and forward its clicks the same way.

Kind: global function

Param
players
viewer

applyPlacingView(players, redactedPlayers, viewer)

Reduces the boards wrapper to just viewer's own player while placement is actively running - nobody else's board or placement panel is sent at all, not just hidden with CSS. A first version of this function did exactly that (hide via a placing-hidden class, every player still present, same shape as the server's own tree), because trimming the array broke json-dom's own position-based forwarding at the time (setForwardEvents/getItemByPath resolved a click by the index a tile sat at in the tree, which a joiner's own trimmed view - always player 0 to themselves, never to the server - did not match: "remote player was unable to click or update anything during placement", a real reported bug, not a guess). Forwarding resolves by a stable id now instead (see json-dom's getItemById, JSON-DOM#95), which does not care whether the two trees are even the same shape, so the actual data-minimizing version is safe again - another player's own placement never needs to reach a viewer's screen at all, closing the devtools-visible gap PR #132's server-side ownership check already defends against independently (this is "also don't send it", not a replacement for "also don't trust it").

Kind: global function

Param Description
players the real players, same order as redactedPlayers - redactPlayer's own siFunciona.cloneObject means a redacted entry is never === viewer, even for viewer's own, so the real references are what decide which one to keep
redactedPlayers
viewer

applyPlayerView(players, redactedPlayers, viewer)

Marks each already-redacted player with which role they play in viewer's own current view (see playerView.ts's roleFor) - only ever adds a class, never touches the board/fleet data itself, which is already correctly redacted either way. The new client-side CSS this enables is what actually turns a summary role plainer (no highlighting, no animation) - nothing here removes any content.

Takes the real (pre-redaction) players alongside their already-redacted clones, in the same order, rather than deciding each role from the redacted list alone: redactPlayer's own siFunciona.cloneObject means a redacted entry is never === viewer any more, even for viewer's own - roleFor's identity check needs the real reference to ever resolve 'own' correctly.

Kind: global function

Param Description
players the real players, same order as redactedPlayers
redactedPlayers
viewer

redactGameBody(body, players, viewer)

A whole screen's worth of game state, redacted for one viewer - the boards wrapper's own children replaced with redactGameState's result, and (for a remote game) the global ordering panel's own Random/Set order controls disabled for anyone but the host - players[0] is always the room's host for as long as any game of theirs is running (the host is always the first to join a room, and the whole room closes if they ever leave, so this holds without needing to thread a separate host id through here). The final-score screen's own Play Again button is left exactly as sent - any player may click it, since all it ever does for its own clicker is show the room's own waiting room again, nothing that needs a host check. Everything else (the robots-only show-all-ships control, if present) is kept as is, since none of it carries anything secret. This is what a remote client actually renders and interacts with: the exact same markup local play already uses, inflated from this instead of built fresh.

While placement is actively running (remotePlacementStage(body) === 'placing'), only viewer's own player is sent at all (see applyPlacingView) - nobody else's board, panel or deadline reaches this viewer's screen during placement. Every other stage (choosing/ordering/shuffling/chosen) still includes and shows everyone, same as today - the host needs to see and click every player's board to set the order. Once placement is over entirely (stage is null), applyPlayerView marks each player's role for this viewer, so the client's own CSS can style a summary player plainer.

Kind: global function

Param
body
players
viewer

connectLobbySocket(url)

The lobby socket, connecting on first use. A test can connect it to its own ephemeral server before triggering any UI action, by calling this directly with that server's URL - the UI's own calls below then reuse that connection.

Kind: global function

Param
url

disconnectLobbySocket()

Close the lobby socket and forget it, so the next connectLobbySocket call starts fresh.

Kind: global function

createRoom()

Create a room as its host, resolving with the room's state once the server acknowledges it, or an error.

Kind: global function

peekRoom()

Look up a room's own lobby name and host's name by its code, without joining it - lets a joiner see whose game it is before they commit their own name (see mainMenu.ts's remote-join-confirm).

Kind: global function

joinRoom()

Join an existing room by its code, resolving with the room's state, or an error if it could not be joined.

Kind: global function

onRoomUpdate()

Be told whenever the room's state changes (a player joins or leaves). Replaces any previous listener rather than adding another - Play Again (see remotePlayAgainListener.ts) re-enters the same waiting room, which would otherwise register a fresh listener each time, piling up duplicate renders on every room update.

Kind: global function

onRoomClosed()

Be told if the host leaves, closing the room for everyone still in it. Replaces any previous listener, for the same reason as onRoomUpdate above.

Kind: global function

getSocketId()

This connection's own socket id, once connected - used to tell whether this player is the room's host.

Kind: global function

startGame()

The host starts the game: every connected player becomes a human player, in the room's own join order, and robots (if any) fill whatever seats are still empty - the server validates this against the room's actual remaining capacity.

Kind: global function

onGameUpdate()

Be told whenever the game's state changes - the redacted view of the whole screen, for this connection alone. Replaces any previous listener rather than adding another - a rematch (see remotePlayAgainListener.ts, server/lobbyServer.ts's relaxed startGame guard) re-enters a second real game on the same connection, which would otherwise leave the first game's own render loop listening alongside the new one, forever.

Kind: global function

sendGameAction()

Forward a user interaction to the server instead of running its listener locally - see setForwardEvents.

Kind: global function

waterTile()

Set the style for tiles representing water: a default (unhit, shipless) tile with an empty point, ready to be given its real point when the board is built.

Kind: global function

shipTile()

Set status and custom properties for tiles that have a ship

Kind: global function

ship(name)

Store properties of a ship which includes an array of all associated ship tiles.

Kind: global function

Param
name

playerStats(player, status, item)

The defined attributes for each player. The name and status run across the top; the player's checkboxes are stacked on the left, and the ship list is on the right.

Kind: global function

Param Description
player
status
item any item already attached to the game's root, to read its settings from - defaults to player, which works once the player is rendered, but not for the first call from buildPlayers (before anything is attached)

playerSet(board, name)

Store the player attributes. board and shipFleet start as placeholders (an empty object, an empty array); they are given their real values once the board is built (see buildPlayers).

Kind: global function

Param
board
name

hitTile()

Set the status of the tile to hit.

Kind: global function

gameTile()

Default properties for a tile in the battleship game.

Kind: global function

showShipsControl()

The one control for the robots-only game: a single checkbox to show every ship on every board, rather than one per board. It sits above the boards.

Kind: global function

remotePlacementPanel()

Each player's own placement controls, rendered as part of their own subtree (not one shared panel at the body level, like local hot-seat's placementPanel) - so every connected player places their own ships on their own board whenever they want, with no "look away" handoff. Redacted per viewer (see redactGameState.ts): only the owning player's own copy is ever enabled - everyone else's is always disabled, showing only ready/not-ready.

Kind: global function

remoteOrderingPanel()

How the turn order is decided once every player has placed: the host picks Random or Set order (clicking each player's board in turn); everyone else sees the exact same status message, with no controls of their own. Enforced server-side (see server/lobbyServer.ts's gameAction handler, which rejects a non-host's action during any stage but placing), not just by these buttons being disabled on a non-host's own redacted copy.

Kind: global function

remoteFinalScore(players)

The final score screen for a remote room's game: the same public score cards local hot-seat shows (see finalScore.ts), plus Play Again (see remotePlayAgainListener.ts) and Leave (see remoteLeaveListener.ts) - both open to every player, neither destructive, so there is no need to restrict who can press either one. Local hot-seat's own separate "Change Settings" button has no remote equivalent: Play Again already returns everyone to the waiting room, where the host can change the hint setting before starting again, so a second button offering the same trip would be redundant.

Kind: global function

Param
players

placementPanel(message)

The panel shown during the placement phase: a message for whoever is placing, and their buttons. Each button has one class name, which is how the layer finds it and how one listener tells them apart (see placementListener).

Kind: global function

Param
message

mainMenu()

The entry screen. It shows the game types (presets) first. Choosing one reveals the lobby, which is the form for the game: how many humans and robots, the hint setting, and who goes first. Start in the lobby submits the form, as before.

Kind: global function

finalScore(players)

Display the final scores after a game has ended, with three ways to go on: Play Again (same settings, places ships again), Change Settings (back to the lobby, pre-filled with this game's settings), and Main Menu (back to choosing the game type).

Kind: global function

Param
players

boards(players)

Wrapper div for player data / boards

Kind: global function

Param
players

update3dCell(config, matrix, x, y, z, isRobot)

Given a cell and new config data, update the data of the cell

Kind: global function

Param Default
config
matrix
x
y
z
isRobot false

shadeShips(player, shown)

Show or hide a player's ships on their board. Ships which have been hit keep the colour they were given when hit, so only the parts not yet hit change.

Kind: global function

Param
player
shown

setShip(matrix, point, view)

Set a specified point to be part of a ship

Kind: global function

Param
matrix
point
view

configureHtml(config, isRobot)

Update view based on actions performed

Kind: global function

Param
config
isRobot

colourHitCell(config)

Colour a cell once it has been hit: red for a ship, white for water. Cells only have a style object once something has resized or highlighted them, so it is created here when it is missing.

Kind: global function

Param
config

markBoard(victim, attackable)

Mark the cells of a board which can still be attacked: the ones not yet hit. The class is what the stylesheet uses to show a target cursor and a highlight when hovered. Tiles keep their own class ('column'), so only this is added.

Kind: global function

Param
victim
attackable

showValidTargets(victim)

Show a human which cells of the board they are attacking can still be hit.

Kind: global function

Param
victim

clearValidTargets(victim)

Remove the markers when the turn passes.

Kind: global function

Param
victim

updateScore(hitShip, sunkShip, players)

Update all game stats after each player round

Kind: global function

Param
hitShip
sunkShip
players

updatePlayerStats(player, status)

Kind: global function

Param
player
status

updatePlayer(player, hitShip, sunkShip)

Track player stats such as attacks and turns

Kind: global function

Param Default
player
hitShip
sunkShip 0

shipsListener(e, target)

The ship toggles. A player's own checkbox (one-player game) shows or hides their ships. The all-ships control (robots-only game) does the same for every board at once.

Kind: global function

Param
e
target

randomAttack(attacker, players)

Attack a random still-unhit cell on a random still-afloat opponent's board - no targeting logic at all, unlike computerAttack.ts's own density-based choice (this is deliberately the dumbest possible fallback, not a robot's turn). Used when a human player's own turn-timeout deadline expires in a remote game (see server/gameplay.ts) instead of leaving everyone else waiting on them indefinitely.

Kind: global function

Param
attacker
players

roleFor(player, viewer)

Which role player plays in viewer's own current view: their own board, a board they can actually attack right now, or everyone else (not their turn, or already eliminated) - still a real board, just styled plainer, no highlighting or animation (see redactGameState.ts's applyPlayerView, which is the one place this actually changes anything sent or rendered). Independent of local vs remote - whatever renders a player's own screen, remote today, local single-player later, decides what to draw for each role; this only decides which role applies.

Kind: global function

Param
player
viewer

applyLocalPlayerView(players, viewer)

Marks every player with which role they play in viewer's own current view (see playerView.ts's roleFor) - the local-play counterpart to redactGameState.ts's applyPlayerView, which does the same thing for a remote viewer's own redacted clone. Local play never redacts data, so this mutates the real tree directly, the same way updatePlayer.ts already mutates a player's own outline/box-shadow style. Only ever changes a className, never touches board/fleet data.

viewer === null means nobody is "own" (robots-only/watch - there is no human playing) - every player gets role-summary unconditionally.

Kind: global function

Param
players
viewer

hintListener(e, target)

The hint checkbox in a human's panel: switching it on shows the heat map at once if it is their turn.

Kind: global function

Param
e
target

victimsOf(player)

The boards a player is attacking: every other player's board.

Kind: global function

Param
player

showHeatHint(victim)

Shade a board's cells by the robot's weighting, as a hint to a human about where a ship may be. The weighting only uses what a player can already see: attacked cells, hit parts and the lengths of unsunk ships.

Kind: global function

Param
victim

clearHeatHint(victim)

Put a board's cells back to their normal border.

Kind: global function

Param
victim

getNextAttacker(attacker, players, hitShip)

Based on the current attacker and list of players, return the next attacker.

Kind: global function

Param
attacker
players
hitShip

findNextAttacker(attacker, players, attackerIndex)

Kind: global function

Param
attacker
players
attackerIndex

endGame(winner)

Final state once a game is won (only one player remains). Local hot-seat's own finalScore screen (Play Again, Change Settings, Main Menu) assumes one physical screen controlling the whole game - a remote room's own game session overrides this via onGameOver (see gameSession.ts, server/gameplay.ts) with something that makes sense for several independent, already-forwarding clients instead.

Kind: global function

Param
winner

getAttackLock(item)

Whether attacks are being ignored right now for the given item's game: the board is locked while the turn changes over. Shared by the code which starts and ends a turn and the code which takes an attack. Each game has its own lock (see gameSession), so one game's turn change never blocks another's.

Kind: global function

Param
item

attackListener(e, target)

target is the board the listener was attached to (see buildPlayers): a DomItem here, like every listener's target, but really always a Board. During the placement phase a click places a ship rather than attacking - local hot-seat's own handoff-based placement, or (for a remote game) simultaneous per-player placement/ordering; exactly one of the two is ever active for a given game, never both.

Kind: global function

Param
e
target

attackFleet(target)

Perform attack on an enemy board / cell

Kind: global function

Param
target

About

This is just a fun project creating a game of battleship. Experimenting with functional programming.

Resources

Stars

0 stars

Watchers

1 watching

Forks

Releases

Packages

Used by

Contributors

Languages