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/
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.
- 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.
- 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
.matrixresize, confirmed via git history -d1be1695); that resize was removed later and nothing on.playeritself 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.
- 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".
doneruns once the order is set, with the order play will take.onTimerChangeruns 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.
doneruns 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
itembelongs 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
highestis 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).targetsis 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
backgroundColordirectly onto a ship tile's ownattributes.stylethe 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
viewerowns it. This is what is safe to send to a remote client forviewer'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 aplacing-hiddenclass, 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 asummaryrole 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 asummaryplayer 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
playerplays inviewer'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 === nullmeans 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
The typed version of json-dom's hasTrait, it narrows an item to the trait it was checked for.
How long players have to finish placing before any still-pending ships are placed at random for them.
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.
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.
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.
Create a default fleet using the standard battleship lengths.
Kind: global variable
| Param |
|---|
| matrix |
| view |
How long each stage of the robot's attack is shown for, in turn.
Extra weight for placements which explain a ship that is already damaged, relative to a fresh ship.
Extra weight for an unattacked cell next to a hit on a ship which is not yet sunk, the partial-hit follow-up.
No server is deployed yet - this only works against a locally-run lobby server (npm run dev:lobby).
Set a visible ship part at the given coordinates (shown with a grey background).
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).
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.
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 |
Given an array of items, return all items which have the lowest status property
Kind: global function
| Param |
|---|
| items |
Return all of the players which have broken ships.
Kind: global function
| Param |
|---|
| players |
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 |
Get all points which were not yet hit in the matrix.
Kind: global function
| Param |
|---|
| matrix |
Given an array of items, return the item with the lowest status property (at the end of the array)
Kind: global function
| Param |
|---|
| items |
Used to generate 'checkerboard' style attack by only attacking every non-edge-touching cell
Kind: global function
| Param |
|---|
| pnt |
Return the hasShip tile boolean at the specified point.
Kind: global function
| Param |
|---|
| pnt |
| matrix |
Return the isHit tile boolean at the specified point.
Kind: global function
| Param |
|---|
| pnt |
| matrix |
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 |
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 |
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 |
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 |
The entry function
Kind: global function
| Param |
|---|
| parent |
Update an item's attributes in place on the real element.
Kind: global function
| Param |
|---|
| item |
| attributes |
Show or hide an item by its display style.
Kind: global function
| Param |
|---|
| item |
| shown |
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 |
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 |
Kind: global function
| Param |
|---|
| e |
| button |
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 |
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 |
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).
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).
Whether only the host may act right now - every stage except placing itself (see server/lobbyServer.ts).
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).
Refresh one player's own placement panel to match their current state.
Mark which cells a ship-in-progress could end on, same visual as local placement's own showStart.
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 |
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 |
A click on a player's own board while the order is being set (see beginOrderSet) - adds them to the order.
A board click while a remote placement/ordering phase is running - routed by the current stage.
Once every player is ready (or the timer below fires), hide their panels and show the ordering choice.
Any player still not ready when the deadline passes has their remaining ships placed for them at random.
The Ready button: locks a player's own fleet in once nothing is left pending.
The Randomise button: re-rolls just this player's own remaining fleet, same as local placement's own version.
The Set order button: from here, clicking each player's board in turn (see pickOrderPlayer) sets the order.
Outline one player's panel, and clear the outline from the others - the random order's own shuffle animation.
A shuffled copy of the players, in a random order - identical to local placement's own version.
The Random button: a short highlight passes over the players, then lands on a full random order.
The order is set (either way) - show it briefly, then begin the round.
The current room's own last known state, if this connection has ever been in one.
Show exactly one of remote-entry's own four sub-panels (see mainMenu.ts), hiding the other three.
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.
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.
Replace the waiting room's player list, lobby name, room code and host controls with a freshly-received room state.
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.
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.
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 |
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 |
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 |
The colour for the player at this position in the game.
Kind: global function
| Param |
|---|
| index |
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 |
The placement buttons: Continue (after the handoff), Randomise, and Done. They are told apart by class name.
Kind: global function
| Param |
|---|
| e |
| target |
The label above a player's board, which shows their place in the order.
Kind: global function
| Param |
|---|
| player |
| text |
The name field on the placement panel.
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 |
Whether a placement phase is running for this item's game, so board clicks are placements rather than attacks.
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 |
Show or hide every player's stats (health, and the hint checkbox), which are not wanted during placement.
Kind: global function
| Param |
|---|
| players |
| shown |
Show only this player's board, or with null hide every board.
Kind: global function
| Param |
|---|
| session |
| player |
Show every board, so the players can see and click each other's.
Continue: from a handoff it starts that player's placement; from the ready screen it starts the round.
Kind: global function
| Param |
|---|
| item |
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 |
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 |
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 |
Clear the current player's board, then place their whole fleet at random.
Kind: global function
| Param |
|---|
| item |
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 |
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 |
Set the order by clicking the boards: each click adds that player to the end of the order.
Kind: global function
| Param |
|---|
| item |
The word for a place in the order: 1st, 2nd, 3rd, then 4th and so on.
Kind: global function
| Param |
|---|
| place |
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 |
Show the order which has been set, and wait for Continue to start.
Kind: global function
| Param |
|---|
| session |
Random: a short highlight passes over the players, then lands on a full random order.
Kind: global function
| Param |
|---|
| item |
A shuffled copy of the players, in a random order.
Kind: global function
| Param |
|---|
| players |
Outline one player's panel, and clear the outline from the others.
Kind: global function
| Param |
|---|
| session |
| chosen |
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 |
The round starts: every board and its stats are shown in turn order, and the placement panel goes.
Kind: global function
| Param |
|---|
| session |
| order |
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 |
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 |
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 |
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 |
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 |
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 |
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 |
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 |
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 |
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 |
Logic for setting up and starting a new round from the lobby form.
Kind: global function
| Param |
|---|
| e |
| mainForm |
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 |
The players with the lowest score, or with the highest when highest is set.
Kind: global function
| Param | Default |
|---|---|
| players | |
| score | |
| highest | false |
The players still afloat, or everyone when none is.
Kind: global function
| Param |
|---|
| 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 |
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 |
Choose which player to attack, using the rule for the game style (elimination for now).
Kind: global function
| Param |
|---|
| players |
| rule |
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 |
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 |
Kind: global function
| Param |
|---|
| victim |
| point |
| color |
Kind: global function
| Param |
|---|
| data |
Colour every row of a player's board, which is how a whole board is outlined.
Kind: global function
| Param |
|---|
| player |
| color |
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 |
Take the display away once the robot has chosen: the heat map and every board outline.
Kind: global function
| Param |
|---|
| victim |
| opponents |
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 |
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 |
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 |
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 |
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 |
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 |
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 |
The unattacked cells which have the highest score. Empty when no score is above zero.
Kind: global function
| Param |
|---|
| scores |
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 |
Main AI logic for computer to attack, selects a target then performs attack function.
Kind: global function
| Param |
|---|
| player |
| players |
Remove every one of a parent's children - the same pattern startNewGame's clearBody uses locally.
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.
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.
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.
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.
Start, update, or stop the visual countdown, as each new deadline (or its absence) comes in.
Remove the countdown entirely - called once the remote game is left.
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 |
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.
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) |
Stop forwarding and clear whatever the remote game last rendered, so the root can go back to running locally.
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 |
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 |
The class name of an item, if it has one - the same lightweight check used throughout this file.
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 |
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 |
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 |
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 |
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 |
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 |
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 |
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 |
Close the lobby socket and forget it, so the next connectLobbySocket call starts fresh.
Create a room as its host, resolving with the room's state once the server acknowledges it, or an error.
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).
Join an existing room by its code, resolving with the room's state, or an error if it could not be joined.
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.
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.
This connection's own socket id, once connected - used to tell whether this player is the room's host.
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.
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.
Forward a user interaction to the server instead of running its listener locally - see setForwardEvents.
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.
Set status and custom properties for tiles that have a ship
Store properties of a ship which includes an array of all associated ship tiles.
Kind: global function
| Param |
|---|
| name |
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) |
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 |
Set the status of the tile to hit.
Default properties for a tile in the battleship game.
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.
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.
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.
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 |
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 |
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.
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 |
Wrapper div for player data / boards
Kind: global function
| Param |
|---|
| players |
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 |
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 |
Set a specified point to be part of a ship
Kind: global function
| Param |
|---|
| matrix |
| point |
| view |
Update view based on actions performed
Kind: global function
| Param |
|---|
| config |
| isRobot |
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 |
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 |
Show a human which cells of the board they are attacking can still be hit.
Kind: global function
| Param |
|---|
| victim |
Remove the markers when the turn passes.
Kind: global function
| Param |
|---|
| victim |
Update all game stats after each player round
Kind: global function
| Param |
|---|
| hitShip |
| sunkShip |
| players |
Kind: global function
| Param |
|---|
| player |
| status |
Track player stats such as attacks and turns
Kind: global function
| Param | Default |
|---|---|
| player | |
| hitShip | |
| sunkShip | 0 |
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 |
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 |
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 |
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 |
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 |
The boards a player is attacking: every other player's board.
Kind: global function
| Param |
|---|
| player |
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 |
Put a board's cells back to their normal border.
Kind: global function
| Param |
|---|
| victim |
Based on the current attacker and list of players, return the next attacker.
Kind: global function
| Param |
|---|
| attacker |
| players |
| hitShip |
Kind: global function
| Param |
|---|
| attacker |
| players |
| attackerIndex |
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 |
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 |
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 |
Perform attack on an enemy board / cell
Kind: global function
| Param |
|---|
| target |