25.3. Reguły poruszania się figur
W grze w szachy istnieją reguły poruszania się po planszy dla poszczególnych rodzajów figur. Te reguły są zaimplementowane w obiekcie FigureMoves z pliku FigureMoves.scala.
Plik FigureMoves.scala: package chess import collection.immutable._, Stream._ object FigureMoves { def rookMoves: Seq[(Stream[Int],Stream[Int])] = Seq((from(1,1),continually(0)), (from(-1,-1),continually(0)), (continually(0),from(1,1)), (continually(0),from(-1,-1))) def bishopMoves: Seq[(Stream[Int],Stream[Int])] = Seq((from(1,1),from(1,1)), (from(-1,-1),from(1,1)), (from(1,1),from(-1,-1)), (from(-1,-1),from(-1,-1))) def queenMoves: Seq[(Stream[Int],Stream[Int])] = rookMoves ++ bishopMoves def knightMoves: Seq[(Stream[Int],Stream[Int])] = Seq((Stream(1),Stream(2)), (Stream(2),Stream(1)), (Stream(-1),Stream(2)), (Stream(2),Stream(-1)), (Stream(-1),Stream(-2)), (Stream(-2),Stream(-1)), (Stream(1),Stream(-2)), (Stream(-2),Stream(1))) def kingMoves: Seq[(Stream[Int],Stream[Int])] = queenMoves.map{case (a,b) => (a.take(1),b.take(1)) } def chooseFigureMoves(figure: Figure, field: Field, capture: Boolean): Seq[(Stream[Int],Stream[Int])] = figure.figureType match { case Rook => rookMoves case Bishop => bishopMoves case Queen => queenMoves case King => kingMoves case Knight => knightMoves case Pawn => capture match {
case false => figure.figureColor match { case White => if (field.row == 2) Seq((continually(0),Stream(1,2))) else Seq((Stream(0),Stream(1))) case Black => if (field.row == 7) Seq((continually(0),Stream(-1,-2))) else Seq((Stream(0),Stream(-1))) } case true => figure.figureColor match { case White => Seq((Stream(-1),Stream(1)),(Stream(1),Stream(1))) case Black => Seq((Stream(-1),Stream(-1)),(Stream(1),Stream(-1))) } } } def relativeField(field: Field)(cr: (Int,Int)): Field = Field(field.col+cr._1, field.row+cr._2) def relativeFields(field: Field)(colsRows: (Stream[Int],Stream[Int])): Stream[Field] = colsRows._1.zip(colsRows._2).map(relativeField(field)).takeWhile(_.isValid) def figureMoves(figure: Figure, field: Field, capture: Boolean): Seq[Stream[Field]] = chooseFigureMoves(figure, field, capture).map(relativeFields(field)) }
Metoda rookMoves zwraca współrzędne względne, dotyczące potencjalnych ruchów wieży. Reguły poruszania się wieży dopuszczają ruch po planszy w pionie i poziomie. Wynikiem wywołania metody są cztery pary zawierające strumienie względnych wartości kolumn i wierszy. Ponieważ te strumienie są nieskończone, w poniższym przykładzie pokazywane są jedynie cztery pierwsze elementy.
scala> import chess._, FigureMoves._
import chess._
import FigureMoves._
scala> rookMoves.map{case (a,b) => (a.take(4).force,b.take(4).force) }
res0: scala.collection.immutable.Seq[(scala.collection.immutable.Stream[Int], scala.collection.immutable.Stream[Int])] = List((Stream(1, 2, 3, 4),Stream(0, 0, 0, 0)), (Stream(-1, -2, -3, -4),Stream(0, 0, 0, 0)), (Stream(0, 0, 0, 0),Stream(1, 2, 3, 4)), (Stream(0, 0, 0, 0),Stream(-1, -2, -3, -4)))
Metoda bishopMoves zwraca podobne strumienie dotyczące ruchów gońca, który może poruszać się po przekątnych.
scala> bishopMoves.map{case (a,b) => (a.take(4).force,b.take(4).force) }
res1: scala.collection.immutable.Seq[(scala.collection.immutable.Stream[Int], scala.collection.immutable.Stream[Int])] = List((Stream(1, 2, 3, 4),Stream(1, 2, 3, 4)), (Stream(-1, -2, -3, -4),Stream(1, 2, 3, 4)), (Stream(1, 2, 3, 4),Stream(-1, -2, -3, -4)), (Stream(-1, -2, -3, -4),Stream(-1, -2, -3, -4)))
Metoda queenMoves zwraca strumienie dotyczące ruchów hetmana. Hetman może poruszać się zarówno poziomo i pionowo, jak i po przekątnych. Możliwe ruchy w przypadku hetmana są połączeniem możliwych ruchów wieży i gońca
scala> queenMoves.map{case (a,b) => (a.take(4).force,b.take(4).force) }
res2: scala.collection.immutable.Seq[(scala.collection.immutable.Stream[Int], scala.collection.immutable.Stream[Int])] = List((Stream(1, 2, 3, 4),Stream(0, 0, 0, 0)), (Stream(-1, -2, -3, -4),Stream(0, 0, 0, 0)), (Stream(0, 0, 0, 0),Stream(1, 2, 3, 4)), (Stream(0, 0, 0, 0),Stream(-1, -2, -3, -4)), (Stream(1, 2, 3, 4),Stream(1, 2, 3, 4)), (Stream(-1, -2, -3, -4),Stream(1, 2, 3, 4)), (Stream(1, 2, 3, 4),Stream(-1, -2, -3, -4)), (Stream(-1, -2, -3, -4),Stream(-1, -2, -3, -4)))
Metoda knightMoves zwraca strumienie dotyczące ruchów skoczka. Skoczek może poruszać się o dwa pola w górę lub dół i równocześnie jedno w lewo lub w prawo albo o dwa pola w lewo lub prawo i jednocześnie jedno pole w górę lub w dół.
scala> knightMoves.map{case (a,b) => (a.force,b.force) }
res3: scala.collection.immutable.Seq[(scala.collection.immutable.Stream[Int], scala.collection.immutable.Stream[Int])] = List((Stream(1),Stream(2)), (Stream(2),Stream(1)), (Stream(-1),Stream(2)), (Stream(2),Stream(-1)), (Stream(-1),Stream(-2)), (Stream(-2),Stream(-1)), (Stream(1),Stream(-2)), (Stream(-2),Stream(1)))
Metoda kingMoves zwraca strumienie dotyczące ruchów króla. Król może poruszać się w tych samych kierunkach co hetman, ale tylko na sąsiednie pole, a więc sekwencje ruchów mają w przypadku króla długość jednego elementu.
scala> kingMoves.map{case (a,b) => (a.force,b.force) }
res4: scala.collection.immutable.Seq[(scala.collection.immutable.Stream[Int], scala.collection.immutable.Stream[Int])] = List((Stream(1),Stream(0)), (Stream(-1),Stream(0)), (Stream(0),Stream(1)), (Stream(0),Stream(-1)), (Stream(1),Stream(1)), (Stream(-1),Stream(1)), (Stream(1),Stream(-1)), (Stream(-1),Stream(-1)))
Metoda chooseFigureMoves zwraca podobne do powyższych strumienie
dotyczące ruchów dowolnej figury, przy czym samodzielnie oblicza
sekwencje dotyczące piona, a w przypadku pozostałych figur deleguje
obliczenia do jednej z metod opisanych powyżej. Dozwolone ruchy piona
zależą od jego koloru, pozycji początkowej oraz tego, czy ruch
powoduje zbicie figury przeciwnika, czy nie. Reguły ruchu piona są
zaimplementowane począwszy od wiersza
.
scala> chooseFigureMoves(Figure(Pawn,White),Field(4,2),false).map{case (a,b) => (a.take(4).force,b.take(4).force) }
res5: scala.collection.immutable.Seq[(scala.collection.immutable.Stream[Int], scala.collection.immutable.Stream[Int])] = List((Stream(0, 0, 0, 0),Stream(1, 2)))
scala> chooseFigureMoves(Figure(Pawn,White),Field(4,4),false).map{case (a,b) => (a.take(4).force,b.take(4).force) }
res6: scala.collection.immutable.Seq[(scala.collection.immutable.Stream[Int], scala.collection.immutable.Stream[Int])] = List((Stream(0),Stream(1)))
Metoda relativeField zwraca nowe pole, mające współrzędne przesunięte w stosunku do danego pola o określoną liczbę kolumn i wierszy. Metoda relativeFields oblicza strumień pól o współrzędnych przesuniętych o określoną liczbę w stosunku do kolumn i wierszy pochodzących z dwóch strumieni liczbowych.
scala> relativeField(Field(1,2))((1,1)) res7: chess.Field = b3 scala> relativeField(Field(1,2))((0,2)) res8: chess.Field = a4 scala> relativeFields(Field(2,2))((Stream(0,0),Stream(1,2))).force res9: scala.collection.immutable.Stream[chess.Field] = Stream(b3, b4)
Wreszcie metoda figureMoves zwraca — dla danej figury, pola na którym stoi oraz w zależności od tego, czy ruch powoduje zbicie figury przeciwnika, czy nie — sekwencje pól, na które dana figura może zostać przemieszczona według reguł ruchu określonych dla tej figury. Oto przykładowe wywołanie metody figureMoves dla białej wieży stojącej na polu c4.
scala> figureMoves(Figure(Rook,White),Field(3,4),false).map(_.force) res10: scala.collection.immutable.Seq[scala.collection.immutable.Stream[chess.Field]] = List(Stream(d4, e4, f4, g4, h4), Stream(b4, a4), Stream(c5, c6, c7, c8), Stream(c3, c2, c1))
Wynikiem wywołania metody jest zbiór czterech sekwencji pól odpowiadających: pięciu kolejnym polom po prawej stronie pola c4 (d4, e4, f4, g4, h4), dwóm po jego lewej stronie (b4, a4), czterem polom nad polem c4 (c5, c6, c7 i c8) i trzem polom pod nim (c3, c2, c1).
Plik FigureMoves.scala:
package chess
import collection.immutable._, Stream._
object FigureMoves {
def rookMoves: Seq[(Stream[Int],Stream[Int])] =
Seq((from(1,1),continually(0)),
(from(-1,-1),continually(0)),
(continually(0),from(1,1)),
(continually(0),from(-1,-1)))
def bishopMoves: Seq[(Stream[Int],Stream[Int])] =
Seq((from(1,1),from(1,1)),
(from(-1,-1),from(1,1)),
(from(1,1),from(-1,-1)),
(from(-1,-1),from(-1,-1)))
def queenMoves: Seq[(Stream[Int],Stream[Int])] = rookMoves ++ bishopMoves
def knightMoves: Seq[(Stream[Int],Stream[Int])] =
Seq((Stream(1),Stream(2)),
(Stream(2),Stream(1)),
(Stream(-1),Stream(2)),
(Stream(2),Stream(-1)),
(Stream(-1),Stream(-2)),
(Stream(-2),Stream(-1)),
(Stream(1),Stream(-2)),
(Stream(-2),Stream(1)))
def kingMoves: Seq[(Stream[Int],Stream[Int])] =
queenMoves.map{case (a,b) => (a.take(1),b.take(1)) }
def chooseFigureMoves(figure: Figure, field: Field, capture: Boolean): Seq[(Stream[Int],Stream[Int])] =
figure.figureType match {
case Rook => rookMoves
case Bishop => bishopMoves
case Queen => queenMoves
case King => kingMoves
case Knight => knightMoves
case Pawn => capture match { 