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).

Język programowania Scala Wydanie 2. Copyright © Grzegorz Balcerek 2016

Licencja Creative Commons

Ten utwór jest dostępny na licencji Creative Commons Uznanie autorstwa-Na tych samych warunkach 4.0 Międzynarodowe.

Oracle and Java are registered trademarks of Oracle and/or its affiliates. Other names may be trademarks of their respective owners.