import { fromEvent, pipe, noop, Subject, BehaviorSubject, merge } from 'rxjs';
import { repeatWhen, delay, filter, map, takeWhile, tap } from 'rxjs/operators';
import { paintBoards, paintScores } from './html-renderer';
import { Boards, ComputerMove } from './interfaces';
export const random = () => Math.floor(Math.random() * Math.floor(GAME_SIZE));
export const validClicks$ = pipe(
map((e: MouseEvent) => e.target['id']),
const playerMove = new Subject();
const computerMove = new BehaviorSubject({ playerBoard: [], hits: {} });
): [number, number, boolean, number] =>
((boardValue): [number, number, boolean, number] => (
(boards[player][x][y] = boardValue === EMPTY ? MISS : HIT),
[x, y, boards[player][x][y] === HIT, boardValue]
))(boards[player][x][y]);
const isValidMove = (boards: Boards, player, x, y): boolean =>
boards[player][x][y] !== HIT && boards[player][x][y] !== MISS;
nextMove: (x, y, wasHit, boardValue) => void
!isValidMove(boards, player, x, y)
? nextMove(x, y, true, boards[player][x][y])
filter(([player, x, y]) => isValidMove(boards, player, x, y)),
map(([_, x, y]) => shot(boards, player, x, y)),
([x, y, wasHit, boardValue]) => (
nextMove(x, y, wasHit, boardValue),
paintScores(computerScore$, playerScore$)
if ([EMPTY, HIT, MISS].some(e => e === boardValue)) {
return computerMove.value;
if (!computerMove.value.hits[boardValue]) {
computerMove.value.hits[boardValue] = [];
computerMove.value.hits[boardValue].push({ x, y });
computerMove.value.playerBoard = playerBoard;
return computerMove.value;
const nextComputerMove = (): [string, number, number] => {
const hits = computerMove.value.hits;
const shipToPursue = Object.keys(hits).find(
e => hits[e].length !== parseInt(e)
return [PLAYER, random(), random()];
const playerBoard = computerMove.value.playerBoard;
const shipHits = hits[shipToPursue];
if (shipHits.length === 1) {
playerBoard[x][y] !== undefined &&
playerBoard[x][y] !== MISS &&
playerBoard[x][y] !== HIT
return [PLAYER, shotCandidates[0][0], shotCandidates[0][1]];
const getOrderedHits = key =>
(orderedHits => [orderedHits[0], orderedHits[orderedHits.length - 1]])(
shipHits.sort((h1, h2) => (h1[key] > h2[key] ? 1 : -1))
const isHorizontal = shipHits.every(e => e.x === shipHits[0].x);
const [min, max] = getOrderedHits('y');
playerBoard[min.x][min.y - 1] !== undefined &&
playerBoard[min.x][min.y - 1] !== HIT &&
playerBoard[min.x][min.y - 1] !== MISS
const [min, max] = getOrderedHits('x');
playerBoard[min.x - 1] !== undefined &&
playerBoard[min.x - 1][min.y] !== HIT &&
playerBoard[min.x - 1][min.y] !== MISS
const initialScore = () => ({
ships: { 5: 5, 4: 4, 3: 3, 2: 2, 1: 1 }
export const playerScore$ = new BehaviorSubject(initialScore());
export const computerScore$ = new BehaviorSubject(initialScore());
export const isNotGameOver = _ =>
computerScore$.value.score < NUMBER_OF_SHIP_PARTS &&
playerScore$.value.score < NUMBER_OF_SHIP_PARTS;
const scoreChange = (subject: BehaviorSubject<any>, boardValue: number) =>
boardValue >= SHORTEST_SHIP && boardValue <= LONGEST_SHIP
? ((subject.value.ships[boardValue] -= 1),
score: subject.value.score + 1,
ships: subject.value.ships
const computerShot$ = (boards: Boards) =>
map(_ => nextComputerMove()),
performShot$(boards, PLAYER, (x, y, wasHit, boardValue) =>
? (scoreChange(computerScore$, boardValue),
computerHits(boards[PLAYER], x, y, wasHit, boardValue)
const playerShot$ = (boards: Boards) =>
fromEvent(document, 'click').pipe(
map((click: string) => click.split(',')),
filter(([player]) => player === COMPUTER),
performShot$(boards, COMPUTER, (x, y, wasHit, boardValue) =>
? scoreChange(playerScore$, boardValue)
: computerMove.next(computerMove.value)
takeWhile(([x, y, wasHit]) => wasHit),
repeatWhen(_ => playerMove)
export const shots$ = (boards: Boards) =>
merge(playerShot$(boards), computerShot$(boards));