1- import { DEFAULT_BOARD_SIZE } from './constants' ;
1+ import { DEFAULT_BOARD_SIZE , NO_HIT } from './constants' ;
22import { Ship , Coordinate , CellState , ShotResult , GameState , Game } from './game.types' ;
33import { EOL } from 'os' ;
44
@@ -69,48 +69,12 @@ export class GameService implements IGameService {
6969 this . ensurePlayerExists ( playerName ) ;
7070 this . ensureWithinBoard ( target ) ;
7171 this . ensurePlayerTurn ( playerName ) ;
72-
73- const gameState = this . _game . states . get ( playerName ) ;
74- if ( ! gameState ) {
75- throw new Error ( `No board found for player: ${ playerName } ` ) ;
76- }
77-
78- const currentState = gameState . board [ target . y ] [ target . x ] ;
79- if ( currentState === CellState . Hit || currentState === CellState . Miss ) {
80- return {
81- hit : false ,
82- message : `Already fired at (${ target . x } , ${ target . y } )` ,
83- } ;
84- }
85-
72+ const game = this . fetchGame ( playerName ) ;
73+ const currentState = game . board [ target . y ] [ target . x ] ;
74+ this . ensureCellNotAlreadyUsed ( currentState , target ) ;
8675 const hit = currentState !== CellState . Empty ;
87- gameState . board [ target . y ] [ target . x ] = hit ? CellState . Hit : CellState . Miss ;
88-
89- if ( hit ) {
90- const coordString = `${ target . x } ,${ target . y } ` ;
91- const ship = this . findShipAtCoordinate ( gameState . ships , target ) ;
92- if ( ship ) {
93- ship . hits . add ( coordString ) ;
94- const shipDestroyed = this . isShipDestroyed ( ship ) ;
95- const gameWon = this . checkForWin ( gameState . ships ) ;
96-
97- if ( gameWon ) {
98- gameState . winner = playerName ;
99- }
100-
101- return {
102- hit : true ,
103- message : shipDestroyed ? 'Ship destroyed!' : 'Hit!' ,
104- shipDestroyed,
105- gameWon,
106- } ;
107- }
108- }
109-
110- return {
111- hit : false ,
112- message : 'Miss!' ,
113- } ;
76+ game . board [ target . y ] [ target . x ] = hit ? CellState . Hit : CellState . Miss ;
77+ return hit ? this . getShotResult ( target , game , playerName ) : NO_HIT ;
11478 }
11579
11680 getWinner ( playerName : string ) : string | null {
@@ -150,26 +114,65 @@ export class GameService implements IGameService {
150114 return lines . join ( EOL ) ;
151115 }
152116
153- private ensurePlayerTurn ( playerName : string ) {
117+ private ensureCellNotAlreadyUsed ( currentState : string , target : Coordinate ) : void {
118+ if ( this . isCellHitOrMissed ( currentState ) ) {
119+ throw new Error ( `Already fired at (${ target . x } , ${ target . y } )` ) ;
120+ }
121+ }
122+
123+ private getShotResult ( target : Coordinate , game : GameState , playerName : string ) : ShotResult {
124+ const ship = this . findShipAtCoordinate ( game . ships , target ) ;
125+ ship . hits . add ( `${ target . x } ,${ target . y } ` ) ;
126+ const shipDestroyed = this . isShipDestroyed ( ship ) ;
127+ const isWinner = this . checkForWin ( game . ships ) ;
128+ if ( isWinner ) {
129+ game . winner = playerName ;
130+ }
131+
132+ return {
133+ hit : true ,
134+ message : shipDestroyed ? 'Ship destroyed!' : 'Hit!' ,
135+ shipDestroyed,
136+ gameWon : isWinner ,
137+ } ;
138+ }
139+
140+ private isCellHitOrMissed ( currentState : string ) : boolean {
141+ return currentState === CellState . Hit || currentState === CellState . Miss ;
142+ }
143+
144+ private fetchGame ( playerName : string ) : GameState {
145+ const gameState = this . _game . states . get ( playerName ) ;
146+ this . ensureGameExists ( gameState , playerName ) ;
147+ return gameState ;
148+ }
149+
150+ private ensureGameExists ( gameState : GameState , playerName : string ) : void {
151+ if ( ! gameState ) {
152+ throw new Error ( `No board found for player: ${ playerName } ` ) ;
153+ }
154+ }
155+
156+ private ensurePlayerTurn ( playerName : string ) : void {
154157 const currentPlayer = this . getCurrentPlayer ( ) ;
155158 if ( currentPlayer !== playerName ) {
156159 throw new Error ( `Not your turn. Current player: ${ currentPlayer } ` ) ;
157160 }
158161 }
159162
160- private ensureNoShipOverlap ( board : string [ ] [ ] , coord : Coordinate ) {
163+ private ensureNoShipOverlap ( board : string [ ] [ ] , coord : Coordinate ) : void {
161164 if ( this . checkForShipOverlap ( board , coord ) ) {
162165 throw new Error ( `Ship overlap at (${ coord . x } , ${ coord . y } )` ) ;
163166 }
164167 }
165168
166- private ensureWithinBoard ( coord : Coordinate ) {
167- if ( this . outsideBoard ( coord ) ) {
169+ private ensureWithinBoard ( coord : Coordinate ) : void {
170+ if ( this . isOutsideBoard ( coord ) ) {
168171 throw new Error ( `Ship placement out of bounds: (${ coord . x } , ${ coord . y } )` ) ;
169172 }
170173 }
171174
172- private ensurePlayerExists ( playerName : string ) {
175+ private ensurePlayerExists ( playerName : string ) : void {
173176 if ( ! this . hasPlayer ( playerName ) ) {
174177 throw new Error ( `Unknown player: ${ playerName } ` ) ;
175178 }
@@ -199,11 +202,11 @@ export class GameService implements IGameService {
199202 return board [ coord . y ] [ coord . x ] !== CellState . Empty ;
200203 }
201204
202- private outsideBoard ( coord : Coordinate ) {
205+ private isOutsideBoard ( coord : Coordinate ) : boolean {
203206 return coord . x < 0 || coord . x >= this . _boardSize || coord . y < 0 || coord . y >= this . _boardSize ;
204207 }
205208
206- private printContent ( board : string [ ] [ ] , lines : string [ ] ) {
209+ private printContent ( board : string [ ] [ ] , lines : string [ ] ) : void {
207210 for ( let y = 0 ; y < this . _boardSize ; y ++ ) {
208211 let row = ` ${ y } |` ;
209212 for ( let x = 0 ; x < this . _boardSize ; x ++ ) {
@@ -213,7 +216,7 @@ export class GameService implements IGameService {
213216 }
214217 }
215218
216- private printHeader ( lines : string [ ] ) {
219+ private printHeader ( lines : string [ ] ) : void {
217220 let header = ' |' ;
218221 for ( let x = 0 ; x < this . _boardSize ; x ++ ) {
219222 header += ` ${ x } |` ;
0 commit comments