11import { DEFAULT_BOARD_SIZE } from './constants' ;
2- import { Ship , Coordinate , CellState , ShotResult , GameState } from './game.types' ;
2+ import { Ship , Coordinate , CellState , ShotResult , GameState , Game } from './game.types' ;
33import { EOL } from 'os' ;
44
55export interface IGameService {
@@ -10,11 +10,13 @@ export interface IGameService {
1010 getBoardSize ( ) : number ;
1111 fire ( playerName : string , target : Coordinate ) : ShotResult ;
1212 getWinner ( playerName : string ) : string | null ;
13+ getCurrentPlayer ( ) : string | null ;
14+ switchTurn ( ) : string ;
1315}
1416
1517export class GameService implements IGameService {
1618 private readonly _players : Set < string > ;
17- private readonly _gameStates : Map < string , GameState > ;
19+ private readonly _game : Game ;
1820 private readonly _boardSize : number ;
1921
2022 constructor ( boardSize : number = DEFAULT_BOARD_SIZE ) {
@@ -23,7 +25,11 @@ export class GameService implements IGameService {
2325 }
2426 this . _boardSize = boardSize ;
2527 this . _players = new Set < string > ( ) ;
26- this . _gameStates = new Map < string , GameState > ( ) ;
28+ this . _game = {
29+ players : [ ] ,
30+ currentPlayerIndex : 0 ,
31+ states : new Map < string , GameState > ( ) ,
32+ } ;
2733 }
2834
2935 getBoardSize ( ) : number {
@@ -34,51 +40,37 @@ export class GameService implements IGameService {
3440 this . ensurePlayerExists ( playerName ) ;
3541 const board = this . createEmptyBoard ( ) ;
3642
37- const shipsWithHits = ships . map ( ( ship ) => ( {
43+ const shipsWithNoHits = ships . map ( ( ship ) => ( {
3844 ...ship ,
3945 hits : new Set < string > ( ) ,
4046 } ) ) ;
4147
42- for ( const ship of shipsWithHits ) {
48+ for ( const ship of shipsWithNoHits ) {
4349 for ( const coord of ship . coordinates ) {
4450 this . ensureWithinBoard ( coord ) ;
4551 this . ensureNoShipOverlap ( board , coord ) ;
46- board [ coord . y ] [ coord . x ] = CellState . Ship ;
52+ board [ coord . y ] [ coord . x ] = ship . type ;
4753 }
4854 }
4955
50- this . _gameStates . set ( playerName , {
51- ships : shipsWithHits ,
56+ const gameState = this . _game . states . get ( playerName ) || {
57+ ships : shipsWithNoHits ,
5258 board,
5359 winner : null ,
54- } ) ;
55- }
56-
57- addPlayer ( name : string ) : void {
58- this . _players . add ( name ) ;
59- }
60-
61- hasPlayer ( name : string ) : boolean {
62- return this . _players . has ( name ) ;
63- }
60+ } ;
61+ this . _game . states . set ( playerName , gameState ) ;
6462
65- printBoard ( playerName : string ) : string {
66- const board = this . _gameStates . get ( playerName ) ?. board ;
67- if ( ! board ) {
68- throw new Error ( `No board found for player: ${ playerName } ` ) ;
63+ if ( ! this . _game . players . includes ( playerName ) ) {
64+ this . _game . players . push ( playerName ) ;
6965 }
70-
71- const lines : string [ ] = [ ] ;
72- this . printHeader ( lines ) ;
73- this . printContent ( board , lines ) ;
74- return lines . join ( EOL ) ;
7566 }
7667
7768 fire ( playerName : string , target : Coordinate ) : ShotResult {
7869 this . ensurePlayerExists ( playerName ) ;
7970 this . ensureWithinBoard ( target ) ;
71+ this . ensurePlayerTurn ( playerName ) ;
8072
81- const gameState = this . _gameStates . get ( playerName ) ;
73+ const gameState = this . _game . states . get ( playerName ) ;
8274 if ( ! gameState ) {
8375 throw new Error ( `No board found for player: ${ playerName } ` ) ;
8476 }
@@ -91,7 +83,7 @@ export class GameService implements IGameService {
9183 } ;
9284 }
9385
94- const hit = currentState === CellState . Ship ;
86+ const hit = currentState !== CellState . Empty ;
9587 gameState . board [ target . y ] [ target . x ] = hit ? CellState . Hit : CellState . Miss ;
9688
9789 if ( hit ) {
@@ -122,10 +114,49 @@ export class GameService implements IGameService {
122114 }
123115
124116 getWinner ( playerName : string ) : string | null {
125- const gameState = this . _gameStates . get ( playerName ) ;
117+ const gameState = this . _game . states . get ( playerName ) ;
126118 return gameState ?. winner ?? null ;
127119 }
128120
121+ getCurrentPlayer ( ) : string | null {
122+ return this . _game . players [ this . _game . currentPlayerIndex ] || null ;
123+ }
124+
125+ switchTurn ( ) : string {
126+ if ( this . _game . players . length < 2 ) {
127+ throw new Error ( 'Need at least 2 players to switch turns' ) ;
128+ }
129+ this . _game . currentPlayerIndex = ( this . _game . currentPlayerIndex + 1 ) % this . _game . players . length ;
130+ return this . getCurrentPlayer ( ) ! ;
131+ }
132+
133+ addPlayer ( name : string ) : void {
134+ this . _players . add ( name ) ;
135+ }
136+
137+ hasPlayer ( name : string ) : boolean {
138+ return this . _players . has ( name ) ;
139+ }
140+
141+ printBoard ( playerName : string ) : string {
142+ const gameState = this . _game . states . get ( playerName ) ;
143+ if ( ! gameState ) {
144+ throw new Error ( `No board found for player: ${ playerName } ` ) ;
145+ }
146+
147+ const lines : string [ ] = [ ] ;
148+ this . printHeader ( lines ) ;
149+ this . printContent ( gameState . board , lines ) ;
150+ return lines . join ( EOL ) ;
151+ }
152+
153+ private ensurePlayerTurn ( playerName : string ) {
154+ const currentPlayer = this . getCurrentPlayer ( ) ;
155+ if ( currentPlayer !== playerName ) {
156+ throw new Error ( `Not your turn. Current player: ${ currentPlayer } ` ) ;
157+ }
158+ }
159+
129160 private ensureNoShipOverlap ( board : string [ ] [ ] , coord : Coordinate ) {
130161 if ( this . checkForShipOverlap ( board , coord ) ) {
131162 throw new Error ( `Ship overlap at (${ coord . x } , ${ coord . y } )` ) ;
@@ -144,6 +175,26 @@ export class GameService implements IGameService {
144175 }
145176 }
146177
178+ private findShipAtCoordinate ( ships : Ship [ ] , target : Coordinate ) : Ship | undefined {
179+ return ships . find ( ( ship ) =>
180+ ship . coordinates . some ( ( coord ) => coord . x === target . x && coord . y === target . y ) ,
181+ ) ;
182+ }
183+
184+ private isShipDestroyed ( ship : Ship ) : boolean {
185+ return ship . coordinates . every ( ( coord ) => ship . hits . has ( `${ coord . x } ,${ coord . y } ` ) ) ;
186+ }
187+
188+ private checkForWin ( ships : Ship [ ] ) : boolean {
189+ return ships . every ( ( ship ) => this . isShipDestroyed ( ship ) ) ;
190+ }
191+
192+ private createEmptyBoard ( ) : string [ ] [ ] {
193+ return Array ( this . _boardSize )
194+ . fill ( null )
195+ . map ( ( ) => Array ( this . _boardSize ) . fill ( CellState . Empty ) ) ;
196+ }
197+
147198 private checkForShipOverlap ( board : string [ ] [ ] , coord : Coordinate ) : boolean {
148199 return board [ coord . y ] [ coord . x ] !== CellState . Empty ;
149200 }
@@ -169,24 +220,4 @@ export class GameService implements IGameService {
169220 }
170221 lines . push ( header ) ;
171222 }
172-
173- private findShipAtCoordinate ( ships : Ship [ ] , target : Coordinate ) : Ship | undefined {
174- return ships . find ( ( ship ) =>
175- ship . coordinates . some ( ( coord ) => coord . x === target . x && coord . y === target . y ) ,
176- ) ;
177- }
178-
179- private isShipDestroyed ( ship : Ship ) : boolean {
180- return ship . coordinates . every ( ( coord ) => ship . hits . has ( `${ coord . x } ,${ coord . y } ` ) ) ;
181- }
182-
183- private checkForWin ( ships : Ship [ ] ) : boolean {
184- return ships . every ( ( ship ) => this . isShipDestroyed ( ship ) ) ;
185- }
186-
187- private createEmptyBoard ( ) : string [ ] [ ] {
188- return Array ( this . _boardSize )
189- . fill ( null )
190- . map ( ( ) => Array ( this . _boardSize ) . fill ( CellState . Empty ) ) ;
191- }
192223}
0 commit comments