From c74f53fce4a9da4734c4958a6df30cae39a6b32e Mon Sep 17 00:00:00 2001
From: overflowerror <mail@overflowerror.com>
Date: Sat, 30 Dec 2023 14:19:47 +0100
Subject: [PATCH] feat: Add queens to game system

---
 src/Game/Queen.php       | 112 ++++++++++++++++++++++++++++++++++++
 tests/Game/QueenTest.php | 119 +++++++++++++++++++++++++++++++++++++++
 2 files changed, 231 insertions(+)
 create mode 100644 src/Game/Queen.php
 create mode 100644 tests/Game/QueenTest.php

diff --git a/src/Game/Queen.php b/src/Game/Queen.php
new file mode 100644
index 0000000..a9cdd8e
--- /dev/null
+++ b/src/Game/Queen.php
@@ -0,0 +1,112 @@
+<?php
+
+namespace Game;
+
+class Queen extends Piece {
+
+    public function getName(): string {
+        return "Queen";
+    }
+
+    public function getShort(): string {
+        return "Q";
+    }
+
+    public function getMoveCandidateMap(FieldBitMap $occupied, FieldBitMap $captureable, FieldBitMap $threatened): FieldBitMap {
+        $result = FieldBitMap::empty();
+
+        $directions = [true, true, true, true];
+        for ($i = 1; $i < 8; $i++) {
+            for ($d = 0; $d < 4; $d++) {
+                if ($directions[$d]) {
+                    $candidate = new Position(
+                        $this->position->file + $i * ($d % 2 == 0 ? 1 : -1),
+                        $this->position->rank + $i * ($d < 2 ? 1 : -1)
+                    );
+                    if ($candidate->isValid()) {
+                        if ($captureable->has($candidate)) {
+                            $result->add($candidate);
+                            $directions[$d] = false;
+                        } else if ($occupied->has($candidate)) {
+                            $directions[$d] = false;
+                        } else {
+                            $result->add($candidate);
+                        }
+                    } else {
+                        $directions[$d] = false;
+                    }
+                }
+            }
+        }
+
+        for ($offset = 1; $offset < 8; $offset++) {
+            $candidate = new Position($this->position->file, $this->position->rank + $offset);
+            if (!$candidate->isValid()) {
+                break;
+            }
+
+            if ($occupied->has($candidate)) {
+                break;
+            }
+
+            $result->add($candidate);
+
+            if ($captureable->has($candidate)) {
+                break;
+            }
+        }
+
+        for ($offset = -1; $offset > -8; $offset--) {
+            $candidate = new Position($this->position->file, $this->position->rank + $offset);
+            if (!$candidate->isValid()) {
+                break;
+            }
+
+            if ($occupied->has($candidate)) {
+                break;
+            }
+
+            $result->add($candidate);
+
+            if ($captureable->has($candidate)) {
+                break;
+            }
+        }
+
+        for ($offset = 1; $offset < 8; $offset++) {
+            $candidate = new Position($this->position->file + $offset, $this->position->rank);
+            if (!$candidate->isValid()) {
+                break;
+            }
+
+            if ($occupied->has($candidate)) {
+                break;
+            }
+
+            $result->add($candidate);
+
+            if ($captureable->has($candidate)) {
+                break;
+            }
+        }
+
+        for ($offset = -1; $offset > -8; $offset--) {
+            $candidate = new Position($this->position->file + $offset, $this->position->rank);
+            if (!$candidate->isValid()) {
+                break;
+            }
+
+            if ($occupied->has($candidate)) {
+                break;
+            }
+
+            $result->add($candidate);
+
+            if ($captureable->has($candidate)) {
+                break;
+            }
+        }
+
+        return $result;
+    }
+}
\ No newline at end of file
diff --git a/tests/Game/QueenTest.php b/tests/Game/QueenTest.php
new file mode 100644
index 0000000..cad7af8
--- /dev/null
+++ b/tests/Game/QueenTest.php
@@ -0,0 +1,119 @@
+<?php
+declare(strict_types=1);
+
+namespace Game;
+
+use PHPUnit\Framework\TestCase;
+
+final class QueenTest extends TestCase {
+
+    public function testMoves_unobstructed() {
+        $subject = new Queen(new Position(
+            3, 4
+        ), Side::WHITE);
+
+        $result = $subject->getMoveCandidateMap(FieldBitMap::empty(), FieldBitMap::empty(), FieldBitMap::empty());
+
+        $this->assertTrue(
+            $result->equals(new FieldBitMap([
+                new Position(3, 0),
+                new Position(3, 1),
+                new Position(3, 2),
+                new Position(3, 3),
+                new Position(3, 5),
+                new Position(3, 6),
+                new Position(3, 7),
+                new Position(0, 4),
+                new Position(1, 4),
+                new Position(2, 4),
+                new Position(4, 4),
+                new Position(5, 4),
+                new Position(6, 4),
+                new Position(7, 4),
+                new Position(2, 3),
+                new Position(1, 2),
+                new Position(0, 1),
+                new Position(4, 5),
+                new Position(5, 6),
+                new Position(6, 7),
+                new Position(4, 3),
+                new Position(5, 2),
+                new Position(6, 1),
+                new Position(7, 0),
+                new Position(2, 5),
+                new Position(1, 6),
+                new Position(0, 7),
+            ]))
+        );
+    }
+
+    public function testMoves_obstructed() {
+        $subject = new Queen(new Position(
+            3, 0
+        ), Side::WHITE);
+
+        $result = $subject->getMoveCandidateMap(
+            new FieldBitMap([
+                new Position(3, 3),
+                new Position(5, 0),
+                new Position(2, 1),
+            ]),
+            new FieldBitMap([
+                new Position(1, 0),
+                new Position(4, 1),
+            ]),
+            FieldBitMap::empty()
+        );
+
+        $this->assertTrue(
+            $result->equals(new FieldBitMap([
+                new Position(3, 1),
+                new Position(3, 2),
+                new Position(1, 0),
+                new Position(2, 0),
+                new Position(4, 0),
+                new Position(4, 1),
+            ]))
+        );
+    }
+
+    public function testCaptureable() {
+        $subject = new Queen(
+            new Position(5, 6),
+            Side::WHITE,
+        );
+
+        $this->assertTrue(
+            $subject->getCaptureableMap(true)->equals(new FieldBitMap([
+                new Position(5, 6)
+            ]))
+        );
+    }
+
+    public function testCaptureMap() {
+        $subject = new Queen(new Position(
+            5, 2
+        ), Side::WHITE);
+
+        $result = $subject->getCaptureMap(
+            new FieldBitMap([
+                new Position(4, 2),
+                new Position(6, 2),
+                new Position(5, 4),
+                new Position(4, 3),
+                new Position(3, 0),
+                new Position(6, 3),
+                new Position(6, 1),
+            ])
+        );
+
+        $this->assertTrue(
+            $result->equals(new FieldBitMap([
+                new Position(5, 0),
+                new Position(5, 1),
+                new Position(5, 3),
+                new Position(4, 1),
+            ]))
+        );
+    }
+}
\ No newline at end of file