scala - oop -- mutual dependency between chessboard and its pieces -
there have been many similar questions on mutual dependencies, each leaves me unsure own design.
i writing chess program learn scala. close relationship between board , pieces makes me wonder if piece object should contain reference board belongs to. approach when wrote chess program in java.
however, means board not defined until has pieces, , vice-versa. codependency no problem if board instance variable can add pieces build board, goes against immutability.
related:
the approach here seem suggest defining rules of piece movement in board object: https://gamedev.stackexchange.com/questions/43681/how-to-avoid-circular-dependencies-between-player-and-world
the highest voted answer here has similar suggestion: two objects dependencies each other. bad?
the selected answer above link different -- moves mutual dependency class definitions interfaces. don't understand why better.
the design here mirrors current approach: https://sourcemaking.com/refactoring/change-bidirectional-association-to-unidirectional
my code:
abstract class piece(val side: side.value, val row: int, val col: int){ val piece_type: piecetype.value //isinstanceof() accomplish same def possible_moves(board: board): list[move] } class board (val pieces: array[array[piece]]){ def this(){ this(defaultboard.setup) //an object builds starting board } }
having pass parameter board piece belongs work, feels wrong.
thanks in advance!
i took liberty redesign classes.
the first thing noticed: piece
isn't piece. there white bishop on top left field. if move top right field, become different piece? - not. therefore, position of piece on board not part of identity.
so refactor class piece
this:
trait piece { def side:side.value def piece_type:piecetype.value }
(i used trait instead of abstract class here leave open implementor how implement 2 methods.)
the information lost in process should put in different type:
case class pieceplacement(piece:piece, row:int, col:int) { def possible_moves(board:board):seq[move] = ??? // why enfore list here? }
now can define board this:
case class board(pieces:indexedseq[indexedseq[piece]] = defaultboard.setup)
(notice how replaced auxiliary constructor default parameter value, , used immutable indexedseq
instead of mutable array
.)
if have dependency between placement of piece , board, can this:
- add board
pieceplacement
:
case class pieceplacement(piece:piece, row:int, col:int, board:board) {...}
- make board create placement instances:
case class board(...) { def place(piece:piece, row:int, col:int):(board,pieceplacement) = ??? }
note return value of place
returns not new pieceplacement
instance, new board
instance, because want work immutable instances.
now, if @ this, question should raised why place
returns pieceplacement
. benefit caller ever have of it? it's pretty board-internal information. therefore, refactor place
method such returns new board
. then, consequently go without placement
type altogether , thereby eliminate mutual dependency.
another thing might want notice place
method cannot implemented. returned board
must new instance, returned pieceplacement
must contain new board
instance. since new board
instance contains pieceplacement
instance, can never created in immutable way.
so follow advice of @jörgwmittag , rid of mutual references. start defining traits boards , pieces, , include absolute minimum of necessary information. example:
trait board { def at(row:int, col:int):option[piece] def withpieceat(piece:piece, row:int, col:int):board def withoutpieceat(row:int, col:int):board } sealed trait move case class movement(startrow:int, startcol:int, endrow:int, endcol:int) extends move case class capture(startrow:int, startcol:int, endrow:int, endcol:int) extends move sealed trait piecetype { def possiblemoves(board:board, row:int, col:int):seq[move] } object pawn extends piecetype {...} object bishop extends piecetype {...} sealed trait piece { def side:side.value def piecetype:piecetype } case class whitepiece(piecetype:piecetype) { def side:side.white } case class blackpiece(piecetype:piecetype) { def side:side.black }
now can start writing code uses traits reason potential moves etc. also, can write classes implement traits. can start off straightforward implementation, , optimize necessary.
for example: every board position has 13 possible states. 1 per chess piece type, times 2 two sides, plus 1 empty state. states enumerable, might optimize enumerating them.
another potential optimization: since board position requires 4 bits modelling it, 1 whole row of board fits int
variable when encoded. therefore, whole board state can represented 8 int
s, or 4 long
s. optimization trade off performance (bit shifting) in favor of memory usage. optimization better algorithm generates huge number of board
instances , gets in danger of getting outofmemoryerror
.
by modelling board , pieces traits rather classes, can exchange implementations, play around them, , see implementation works best use case - without having change bit of algorithms make use of boards , pieces.
bottom line: introduce things methods , variables when needed. every line of code don't write line of code cannot contain bug. don't worry mutual dependencies between objects when not strictly necessary. , in case of chess board model, not necessary.
focus on simplicity first. every single method, class , parameter should justify existence.
for example, in model proposed, there 2 parameters position: row
, col
. since there 64 possible positions, argue make position
type. can anyval
type compiled int
, example. then, wouldn't need nested structure store board. can store 64 board placement information objects, that's it.
introduce minimum necessary, , extend when needed. in extreme case, start empty traits , add methods when cannot go on without them. while you're @ it, write unit tests every single method. way, should good, clean , reusable solution. key of reusability is: avoid features as can. every single feature introduce restricts versatility. put in strictly required.
Comments
Post a Comment