diff --git a/LICENSE b/LICENSE
deleted file mode 100644
index 6393062..0000000
--- a/LICENSE
+++ /dev/null
@@ -1,21 +0,0 @@
-MIT License
-
-Copyright (c) 2025 wilkolbrzym-coder
-
-Permission is hereby granted, free of charge, to any person obtaining a copy
-of this software and associated documentation files (the "Software"), to deal
-in the Software without restriction, including without limitation the rights
-to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
-copies of the Software, and to permit persons to whom the Software is
-furnished to do so, subject to the following conditions:
-
-The above copyright notice and this permission notice shall be included in all
-copies or substantial portions of the Software.
-
-THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
-IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
-FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
-AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
-LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
-OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
-SOFTWARE.
diff --git a/README.md b/README.md
deleted file mode 100644
index fda54e3..0000000
--- a/README.md
+++ /dev/null
@@ -1 +0,0 @@
-# Battle-Ship
\ No newline at end of file
diff --git a/ai/bot.go b/ai/bot.go
new file mode 100644
index 0000000..ff20675
--- /dev/null
+++ b/ai/bot.go
@@ -0,0 +1,444 @@
+package ai
+
+import (
+ "battleship/game"
+ "fmt"
+ "math"
+ "math/rand"
+ "time"
+)
+
+const (
+ MaxHypotheses = 50000
+ MinHypotheses = 5000
+)
+
+// QuantumHunter implementuje zaawansowany silnik AI.
+type QuantumHunter struct {
+ BoardSize int
+ Config game.GameConfig
+ OpponentGrid [][]game.CellState // Widok planszy przeciwnika (znane informacje)
+ ProbMap [][]float64 // Mapa prawdopodobieństwa
+ ActiveHypotheses [][][]game.CellState // Pula aktywnych, możliwych układów plansz
+ RemainingShips []game.ShipDef // Statki, które pozostały do zatopienia
+}
+
+// NewQuantumHunter tworzy nową instancję bota.
+func NewQuantumHunter(config game.GameConfig) *QuantumHunter {
+ grid := make([][]game.CellState, config.Size)
+ for i := range grid {
+ grid[i] = make([]game.CellState, config.Size)
+ for j := range grid[i] {
+ grid[i][j] = game.CellWater // Water = Unknown
+ }
+ }
+
+ qh := &QuantumHunter{
+ BoardSize: config.Size,
+ Config: config,
+ OpponentGrid: grid,
+ RemainingShips: append([]game.ShipDef{}, config.Ships...),
+ ActiveHypotheses: [][][]game.CellState{},
+ }
+
+ // Wstępna generacja puli hipotez
+ fmt.Println("AI: Generowanie wstępnej puli rzeczywistości kwantowych...")
+ qh.ReplenishHypotheses()
+ return qh
+}
+
+// UpdateGameState aktualizuje wiedzę bota o planszy i filtruje hipotezy.
+func (qh *QuantumHunter) UpdateGameState(x, y int, result game.CellState) {
+ qh.OpponentGrid[y][x] = result
+
+ // Jeśli statek zatonął, zaktualizuj listę i oznacz otoczenie
+ if result == game.CellSunk {
+ size := qh.deduceSunkShipSize(x, y)
+ if size > 0 {
+ qh.removeShipFromRemaining(size)
+ }
+ // Autouzupełnianie wirtualne dla celów filtrowania
+ // (Gra robi to sama w logice, ale bot musi wiedzieć, że otoczenie to 'Miss')
+ // W game/logic.go Fire() robi MarkSurroundingAsMiss.
+ // Ale my dostajemy tylko wynik strzału w (x,y).
+ // Musimy zsynchronizować wiedzę bota.
+ qh.markSurroundingAsMiss(x, y)
+ }
+
+ // Filtrowanie hipotez: usuń te, które są sprzeczne z nowym faktem
+ qh.filterHypotheses(x, y, result)
+
+ // Dogeneruj, jeśli pula jest za mała
+ if len(qh.ActiveHypotheses) < MinHypotheses {
+ qh.ReplenishHypotheses()
+ }
+}
+
+func (qh *QuantumHunter) filterHypotheses(x, y int, result game.CellState) {
+ var valid [][][]game.CellState
+ for _, grid := range qh.ActiveHypotheses {
+ match := false
+ cell := grid[y][x]
+
+ switch result {
+ case game.CellMiss:
+ // Hipoteza musi mieć tu puste pole (nie statek)
+ if cell != game.CellShip && cell != game.CellHit { // Hit w hipotezie to statek
+ match = true
+ }
+ case game.CellHit, game.CellSunk:
+ // Hipoteza musi mieć tu statek
+ if cell == game.CellShip || cell == game.CellHit {
+ match = true
+ }
+ }
+
+ // Dodatkowe sprawdzenie dla Sunk: czy w hipotezie ten statek jest faktycznie takiej długości?
+ // To jest trudne obliczeniowo dla 50k hipotez, pomijamy w tej wersji dla wydajności.
+ // Wystarczy zgodność pozycyjna.
+
+ if match {
+ valid = append(valid, grid)
+ }
+ }
+ qh.ActiveHypotheses = valid
+}
+
+// ReplenishHypotheses dogenerowuje hipotezy do limitu MaxHypotheses.
+func (qh *QuantumHunter) ReplenishHypotheses() {
+ needed := MaxHypotheses - len(qh.ActiveHypotheses)
+ if needed <= 0 {
+ return
+ }
+
+ // Przygotuj płaską listę statków do rozmieszczenia
+ var shipsToPlace []int
+ for _, s := range qh.RemainingShips {
+ for i := 0; i < s.Count; i++ {
+ shipsToPlace = append(shipsToPlace, s.Size)
+ }
+ }
+
+ if len(shipsToPlace) == 0 { return }
+
+ rand.Seed(time.Now().UnixNano())
+
+ // Użyj wielu wątków (goroutines) do generowania, bo to CPU heavy
+ // Ale dla prostoty w TUI (single threaded update loop), zrobimy to sekwencyjnie
+ // lub w małych paczkach. Tutaj sekwencyjnie, bo Go jest szybkie.
+ // 50k może zająć 1-2 sekundy.
+
+ // Bufor na nowe hipotezy
+ newHypotheses := make([][][]game.CellState, 0, needed)
+
+ timeout := time.After(2 * time.Second) // Limit czasu, żeby nie zamrozić UI na długo
+
+ generationLoop:
+ for i := 0; i < needed; i++ {
+ select {
+ case <-timeout:
+ break generationLoop
+ default:
+ // Generuj losowy układ
+ grid := make([][]game.CellState, qh.BoardSize)
+ for r := range grid {
+ grid[r] = make([]game.CellState, qh.BoardSize)
+ for c := range grid[r] {
+ // Inicjalizuj stanem wiedzy (żeby nie stawiać na Miss)
+ // Ale generator tryPlaceShipsRandomly bierze pod uwagę OpponentGrid
+ // więc tu możemy dać Water, a generator sprawdzi.
+ grid[r][c] = game.CellWater
+ }
+ }
+
+ if qh.tryPlaceShipsRandomly(grid, shipsToPlace) {
+ newHypotheses = append(newHypotheses, grid)
+ }
+ }
+ }
+
+ qh.ActiveHypotheses = append(qh.ActiveHypotheses, newHypotheses...)
+}
+
+// CalculateProbabilityMap tworzy mapę na podstawie aktywnych hipotez.
+func (qh *QuantumHunter) CalculateProbabilityMap() {
+ // Reset mapy
+ qh.ProbMap = make([][]float64, qh.BoardSize)
+ for i := range qh.ProbMap {
+ qh.ProbMap[i] = make([]float64, qh.BoardSize)
+ }
+
+ count := float64(len(qh.ActiveHypotheses))
+ if count == 0 { return }
+
+ for _, grid := range qh.ActiveHypotheses {
+ for y := 0; y < qh.BoardSize; y++ {
+ for x := 0; x < qh.BoardSize; x++ {
+ if grid[y][x] == game.CellShip || grid[y][x] == game.CellHit {
+ qh.ProbMap[y][x]++
+ }
+ }
+ }
+ }
+
+ // Normalizacja
+ for y := 0; y < qh.BoardSize; y++ {
+ for x := 0; x < qh.BoardSize; x++ {
+ qh.ProbMap[y][x] /= count
+ }
+ }
+}
+
+func (qh *QuantumHunter) tryPlaceShipsRandomly(grid [][]game.CellState, ships []int) bool {
+ // Kopia ships do tasowania
+ sh := make([]int, len(ships))
+ copy(sh, ships)
+ rand.Shuffle(len(sh), func(i, j int) { sh[i], sh[j] = sh[j], sh[i] })
+
+ for _, size := range sh {
+ placed := false
+ for attempt := 0; attempt < 50; attempt++ {
+ isVertical := rand.Intn(2) == 0
+ x := rand.Intn(qh.BoardSize)
+ y := rand.Intn(qh.BoardSize)
+
+ if isVertical { if y+size > qh.BoardSize { continue } } else { if x+size > qh.BoardSize { continue } }
+
+ if qh.canPlaceShipSimulation(grid, x, y, size, isVertical) {
+ for k := 0; k < size; k++ {
+ cx, cy := x, y
+ if isVertical { cy += k } else { cx += k }
+ grid[cy][cx] = game.CellShip
+ }
+ placed = true
+ break
+ }
+ }
+ if !placed { return false }
+ }
+
+ // Weryfikacja zgodności z OpponentGrid (czy pokrywa wszystkie Hity)
+ for y := 0; y < qh.BoardSize; y++ {
+ for x := 0; x < qh.BoardSize; x++ {
+ // Jeśli wiemy, że tam jest HIT/SUNK, to w wygenerowanym gridzie musi być statek
+ if qh.OpponentGrid[y][x] == game.CellHit || qh.OpponentGrid[y][x] == game.CellSunk {
+ if grid[y][x] != game.CellShip {
+ return false
+ }
+ }
+ }
+ }
+ return true
+}
+
+func (qh *QuantumHunter) canPlaceShipSimulation(grid [][]game.CellState, x, y, size int, isVertical bool) bool {
+ for i := 0; i < size; i++ {
+ cx, cy := x, y
+ if isVertical { cy += i } else { cx += i }
+
+ // Sprawdź kolizje ze znaną wiedzą
+ known := qh.OpponentGrid[cy][cx]
+ if known == game.CellMiss { return false } // Nie możemy stawiać na pudle
+ // Hit/Sunk jest OK (nadpisujemy, bo to statek)
+
+ // Sprawdź kolizje z już postawionymi statkami w tej symulacji
+ current := grid[cy][cx]
+ if current == game.CellShip { return false }
+
+ // Sprawdź otoczenie (No touching)
+ for dy := -1; dy <= 1; dy++ {
+ for dx := -1; dx <= 1; dx++ {
+ nx, ny := cx+dx, cy+dy
+ if nx >= 0 && nx < qh.BoardSize && ny >= 0 && ny < qh.BoardSize {
+ // Ignoruj segmenty tego samego statku (same-ship check)
+ // Sprawdzamy czy (nx,ny) jest częścią statku, który właśnie kładziemy
+ isSelf := false
+ for k := 0; k < size; k++ {
+ sx, sy := x, y
+ if isVertical { sy += k } else { sx += k }
+ if nx == sx && ny == sy { isSelf = true; break }
+ }
+ if isSelf { continue }
+
+ if grid[ny][nx] == game.CellShip { return false }
+
+ // Ważne: Sprawdź też, czy otoczenie nie jest Sunk/Hit w OpponentGrid
+ // Jeśli jest Hit obok, a my go nie przykrywamy (bo !isSelf), to znaczy że dotykamy innego statku.
+ // Jeśli OpponentGrid[ny][nx] == Hit/Sunk -> to jest inny statek.
+ neighborKnown := qh.OpponentGrid[ny][nx]
+ if neighborKnown == game.CellHit || neighborKnown == game.CellSunk {
+ return false
+ }
+ }
+ }
+ }
+ }
+ return true
+}
+
+// GetBestMove zwraca koordynaty strzału i pewność (0-1).
+func (qh *QuantumHunter) GetBestMove() (int, int, float64) {
+ // Upewnij się, że mamy hipotezy
+ if len(qh.ActiveHypotheses) < MinHypotheses {
+ qh.ReplenishHypotheses()
+ }
+
+ qh.CalculateProbabilityMap()
+
+ bestX, bestY := -1, -1
+ maxProb := -1.0
+
+ // Parity - polowanie na szachownicy
+ minShipSize := 100
+ for _, s := range qh.RemainingShips {
+ if s.Count > 0 && s.Size < minShipSize { minShipSize = s.Size }
+ }
+ if minShipSize == 100 { minShipSize = 1 }
+
+ for y := 0; y < qh.BoardSize; y++ {
+ for x := 0; x < qh.BoardSize; x++ {
+ // Strzelamy tylko w nieznane
+ if qh.OpponentGrid[y][x] != game.CellWater { continue }
+
+ prob := qh.ProbMap[y][x]
+
+ // Bonus za parzystość (tylko w trybie Hunt, czyli gdy prob < 1.0)
+ // W trybie Target, prob będzie bliskie 1.0 dla przedłużeń linii, więc to nie zaszkodzi
+ if prob < 0.9 && (x+y)%minShipSize == 0 {
+ prob *= 1.2
+ }
+
+ if prob > maxProb {
+ maxProb = prob
+ bestX, bestY = x, y
+ }
+ }
+ }
+
+ if bestX == -1 {
+ // Fallback
+ return qh.getRandomMove()
+ }
+
+ return bestX, bestY, maxProb
+}
+
+func (qh *QuantumHunter) getRandomMove() (int, int, float64) {
+ candidates := []game.Position{}
+ for y := 0; y < qh.BoardSize; y++ {
+ for x := 0; x < qh.BoardSize; x++ {
+ if qh.OpponentGrid[y][x] == game.CellWater {
+ candidates = append(candidates, game.Position{X: x, Y: y})
+ }
+ }
+ }
+ if len(candidates) == 0 { return 0, 0, 0 }
+ c := candidates[rand.Intn(len(candidates))]
+ return c.X, c.Y, 0.1
+}
+
+// Helpery
+func (qh *QuantumHunter) deduceSunkShipSize(x, y int) int {
+ // Identyczne jak wcześniej
+ visited := make(map[[2]int]bool)
+ queue := [][2]int{{x, y}}
+ visited[[2]int{x, y}] = true
+ count := 0
+ for len(queue) > 0 {
+ curr := queue[0]; queue = queue[1:]
+ count++
+ for _, d := range [][2]int{{0, 1}, {0, -1}, {1, 0}, {-1, 0}} {
+ nx, ny := curr[0]+d[0], curr[1]+d[1]
+ if nx >= 0 && nx < qh.BoardSize && ny >= 0 && ny < qh.BoardSize {
+ if qh.OpponentGrid[ny][nx] == game.CellSunk && !visited[[2]int{nx, ny}] {
+ visited[[2]int{nx, ny}] = true
+ queue = append(queue, [2]int{nx, ny})
+ }
+ }
+ }
+ }
+ return count
+}
+
+func (qh *QuantumHunter) removeShipFromRemaining(size int) {
+ for i, s := range qh.RemainingShips {
+ if s.Size == size && s.Count > 0 {
+ qh.RemainingShips[i].Count--
+ return
+ }
+ }
+}
+
+func (qh *QuantumHunter) markSurroundingAsMiss(x, y int) {
+ // BFS po sunk statku
+ queue := [][2]int{{x, y}}
+ visited := map[[2]int]bool{{x, y}: true}
+ shipCells := [][2]int{{x, y}}
+
+ // 1. Znajdź cały statek
+ idx := 0
+ for idx < len(queue) {
+ curr := queue[idx]; idx++
+ for _, d := range [][2]int{{0, 1}, {0, -1}, {1, 0}, {-1, 0}} {
+ nx, ny := curr[0]+d[0], curr[1]+d[1]
+ if nx >= 0 && nx < qh.BoardSize && ny >= 0 && ny < qh.BoardSize {
+ if qh.OpponentGrid[ny][nx] == game.CellSunk && !visited[[2]int{nx, ny}] {
+ visited[[2]int{nx, ny}] = true
+ queue = append(queue, [2]int{nx, ny})
+ shipCells = append(shipCells, [2]int{nx, ny})
+ }
+ }
+ }
+ }
+
+ // 2. Oznacz otoczenie
+ for _, cell := range shipCells {
+ cx, cy := cell[0], cell[1]
+ for dy := -1; dy <= 1; dy++ {
+ for dx := -1; dx <= 1; dx++ {
+ nx, ny := cx+dx, cy+dy
+ if nx >= 0 && nx < qh.BoardSize && ny >= 0 && ny < qh.BoardSize {
+ if qh.OpponentGrid[ny][nx] == game.CellWater {
+ qh.OpponentGrid[ny][nx] = game.CellMiss
+ }
+ }
+ }
+ }
+ }
+}
+
+// Genetic Layout - to samo co wcześniej, bez zmian
+func (qh *QuantumHunter) GetOptimizedLayout() *game.Board {
+ bestBoard := game.NewBoard(qh.BoardSize)
+ bestFitness := -1.0
+ for i := 0; i < 100; i++ {
+ board := game.NewBoard(qh.BoardSize)
+ if err := board.PlaceShipsRandomly(qh.Config); err == nil {
+ fit := calculateFitness(board)
+ if fit > bestFitness { bestFitness = fit; bestBoard = board }
+ }
+ }
+ return bestBoard
+}
+
+func calculateFitness(b *game.Board) float64 {
+ distSum := 0.0
+ count := 0
+ for i := 0; i < len(b.Ships); i++ {
+ for j := i + 1; j < len(b.Ships); j++ {
+ s1, s2 := b.Ships[i], b.Ships[j]
+ cx1, cy1 := getCenter(s1)
+ cx2, cy2 := getCenter(s2)
+ distSum += math.Sqrt(math.Pow(cx1-cx2, 2) + math.Pow(cy1-cy2, 2))
+ count++
+ }
+ }
+ if count == 0 { return 0 }
+ return distSum / float64(count)
+}
+
+func getCenter(s *game.Ship) (float64, float64) {
+ sumX, sumY := 0, 0
+ for _, p := range s.Positions { sumX += p.X; sumY += p.Y }
+ return float64(sumX)/float64(len(s.Positions)), float64(sumY)/float64(len(s.Positions))
+}
diff --git a/battleship b/battleship
new file mode 100755
index 0000000..42fed94
Binary files /dev/null and b/battleship differ
diff --git a/game/logic.go b/game/logic.go
new file mode 100644
index 0000000..4eab4f4
--- /dev/null
+++ b/game/logic.go
@@ -0,0 +1,190 @@
+package game
+
+import (
+ "fmt"
+ "math/rand"
+ "time"
+)
+
+// PlaceShipsRandomly rozmieszcza statki losowo na planszy zgodnie z zasadami.
+func (b *Board) PlaceShipsRandomly(config GameConfig) error {
+ rand.Seed(time.Now().UnixNano())
+ b.Grid = make([][]Cell, b.Size)
+ for i := range b.Grid {
+ b.Grid[i] = make([]Cell, b.Size)
+ for j := range b.Grid[i] {
+ b.Grid[i][j] = Cell{State: CellWater, ShipID: -1}
+ }
+ }
+ b.Ships = []*Ship{}
+ shipIDCounter := 0
+
+ // Sortowanie statków od największego do najmniejszego dla łatwiejszego upakowania
+ // W Go mapy są nieuporządkowane, więc musimy spłaszczyć listę
+ var shipsToPlace []ShipDef
+ for _, def := range config.Ships {
+ for i := 0; i < def.Count; i++ {
+ shipsToPlace = append(shipsToPlace, def)
+ }
+ }
+
+ // Proste sortowanie bąbelkowe malejąco po rozmiarze
+ for i := 0; i < len(shipsToPlace)-1; i++ {
+ for j := 0; j < len(shipsToPlace)-i-1; j++ {
+ if shipsToPlace[j].Size < shipsToPlace[j+1].Size {
+ shipsToPlace[j], shipsToPlace[j+1] = shipsToPlace[j+1], shipsToPlace[j]
+ }
+ }
+ }
+
+ for _, shipDef := range shipsToPlace {
+ placed := false
+ attempts := 0
+ for !placed && attempts < 10000 { // Zwiększona liczba prób
+ attempts++
+ isVertical := rand.Intn(2) == 0
+ x := rand.Intn(b.Size)
+ y := rand.Intn(b.Size)
+
+ if isVertical {
+ if y+shipDef.Size > b.Size {
+ continue
+ }
+ } else {
+ if x+shipDef.Size > b.Size {
+ continue
+ }
+ }
+
+ if b.CanPlaceShip(x, y, shipDef.Size, isVertical) {
+ newShip := &Ship{
+ ID: shipIDCounter,
+ Name: shipDef.Name,
+ Size: shipDef.Size,
+ Positions: []Position{},
+ Hits: 0,
+ IsSunk: false,
+ }
+ shipIDCounter++
+
+ for i := 0; i < shipDef.Size; i++ {
+ cx, cy := x, y
+ if isVertical {
+ cy += i
+ } else {
+ cx += i
+ }
+ b.Grid[cy][cx].State = CellShip
+ b.Grid[cy][cx].ShipID = newShip.ID
+ newShip.Positions = append(newShip.Positions, Position{X: cx, Y: cy})
+ }
+ b.Ships = append(b.Ships, newShip)
+ placed = true
+ }
+ }
+ if !placed {
+ return fmt.Errorf("nie udało się rozmieścić statków po %d próbach", attempts)
+ }
+ }
+ return nil
+}
+
+// CanPlaceShip sprawdza, czy można umieścić statek w danym miejscu (uwzględniając odstępy).
+// Zasada: statki nie mogą się stykać, nawet rogami.
+func (b *Board) CanPlaceShip(x, y, size int, isVertical bool) bool {
+ for i := 0; i < size; i++ {
+ cx, cy := x, y
+ if isVertical {
+ cy += i
+ } else {
+ cx += i
+ }
+
+ // Sprawdź sąsiedztwo 3x3 dla każdego segmentu statku
+ for dy := -1; dy <= 1; dy++ {
+ for dx := -1; dx <= 1; dx++ {
+ nx, ny := cx+dx, cy+dy
+ if nx >= 0 && nx < b.Size && ny >= 0 && ny < b.Size {
+ if b.Grid[ny][nx].State != CellWater {
+ return false
+ }
+ }
+ }
+ }
+ }
+ return true
+}
+
+// Fire wykonuje strzał w dane pole. Zwraca wynik strzału i komunikat.
+func (b *Board) Fire(x, y int) (CellState, string) {
+ if x < 0 || x >= b.Size || y < 0 || y >= b.Size {
+ return CellWater, "Poza planszą"
+ }
+
+ cell := &b.Grid[y][x]
+ if cell.State == CellHit || cell.State == CellMiss || cell.State == CellSunk {
+ return cell.State, "Już strzelano"
+ }
+
+ if cell.State == CellWater {
+ cell.State = CellMiss
+ return CellMiss, "Pudło"
+ }
+
+ if cell.State == CellShip {
+ cell.State = CellHit
+ // Znajdź statek
+ var hitShip *Ship
+ for _, s := range b.Ships {
+ if s.ID == cell.ShipID {
+ hitShip = s
+ break
+ }
+ }
+
+ if hitShip != nil {
+ hitShip.Hits++
+ if hitShip.Hits >= hitShip.Size {
+ hitShip.IsSunk = true
+ // Oznacz wszystkie pola statku jako zatopione
+ for _, pos := range hitShip.Positions {
+ b.Grid[pos.Y][pos.X].State = CellSunk
+ }
+ // Oznacz otoczenie jako pudła (autouzupełnianie)
+ b.MarkSurroundingAsMiss(hitShip)
+ return CellSunk, "Zatopiony!"
+ }
+ return CellHit, "Trafiony!"
+ }
+ // To nie powinno się zdarzyć
+ return CellHit, "Błąd - statek widmo"
+ }
+
+ return CellWater, "Nieznany stan"
+}
+
+// MarkSurroundingAsMiss oznacza pola wokół zatopionego statku jako pudła.
+func (b *Board) MarkSurroundingAsMiss(ship *Ship) {
+ for _, pos := range ship.Positions {
+ for dy := -1; dy <= 1; dy++ {
+ for dx := -1; dx <= 1; dx++ {
+ nx, ny := pos.X+dx, pos.Y+dy
+ if nx >= 0 && nx < b.Size && ny >= 0 && ny < b.Size {
+ if b.Grid[ny][nx].State == CellWater {
+ b.Grid[ny][nx].State = CellMiss
+ }
+ }
+ }
+ }
+ }
+}
+
+// AllShipsSunk sprawdza, czy wszystkie statki zostały zatopione.
+func (b *Board) AllShipsSunk() bool {
+ for _, s := range b.Ships {
+ if !s.IsSunk {
+ return false
+ }
+ }
+ return true
+}
diff --git a/game/types.go b/game/types.go
new file mode 100644
index 0000000..f12ac8d
--- /dev/null
+++ b/game/types.go
@@ -0,0 +1,83 @@
+package game
+
+// BoardSize definiuje rozmiar planszy.
+const BoardSize = 10
+
+// CellState reprezentuje stan pojedynczego pola na planszy.
+type CellState int
+
+const (
+ CellWater CellState = iota
+ CellShip
+ CellHit
+ CellMiss
+ CellSunk
+)
+
+// Cell reprezentuje komórkę planszy.
+type Cell struct {
+ State CellState
+ ShipID int // -1 jeśli brak statku
+}
+
+// Ship reprezentuje statek.
+type Ship struct {
+ ID int
+ Name string
+ Size int
+ Positions []Position
+ Hits int
+ IsSunk bool
+}
+
+// Position reprezentuje koordynaty (X, Y).
+type Position struct {
+ X, Y int
+}
+
+// GameConfig przechowuje konfigurację gry (statki).
+type GameConfig struct {
+ Size int
+ Ships []ShipDef
+}
+
+// ShipDef definiuje typ statku (nazwa, rozmiar, ilość).
+type ShipDef struct {
+ Name string
+ Size int
+ Count int
+}
+
+// DefaultConfig to konfiguracja 10x10 z JS.
+var DefaultConfig = GameConfig{
+ Size: BoardSize,
+ Ships: []ShipDef{
+ {"Carrier", 5, 1},
+ {"Battleship", 4, 1},
+ {"Cruiser", 3, 2},
+ {"Destroyer", 2, 1},
+ },
+}
+
+// Board reprezentuje planszę do gry.
+type Board struct {
+ Size int
+ Grid [][]Cell
+ Ships []*Ship
+}
+
+// NewBoard tworzy nową planszę.
+func NewBoard(size int) *Board {
+ grid := make([][]Cell, size)
+ for i := range grid {
+ grid[i] = make([]Cell, size)
+ for j := range grid[i] {
+ grid[i][j] = Cell{State: CellWater, ShipID: -1}
+ }
+ }
+ return &Board{
+ Size: size,
+ Grid: grid,
+ Ships: []*Ship{},
+ }
+}
diff --git a/go.mod b/go.mod
new file mode 100644
index 0000000..42bee3f
--- /dev/null
+++ b/go.mod
@@ -0,0 +1,25 @@
+module battleship
+
+go 1.24.3
+
+require (
+ github.com/aymanbagabas/go-osc52/v2 v2.0.1 // indirect
+ github.com/charmbracelet/bubbletea v1.3.10 // indirect
+ github.com/charmbracelet/colorprofile v0.2.3-0.20250311203215-f60798e515dc // indirect
+ github.com/charmbracelet/lipgloss v1.1.0 // indirect
+ github.com/charmbracelet/x/ansi v0.10.1 // indirect
+ github.com/charmbracelet/x/cellbuf v0.0.13-0.20250311204145-2c3ea96c31dd // indirect
+ github.com/charmbracelet/x/term v0.2.1 // indirect
+ github.com/erikgeiser/coninput v0.0.0-20211004153227-1c3628e74d0f // indirect
+ github.com/lucasb-eyer/go-colorful v1.2.0 // indirect
+ github.com/mattn/go-isatty v0.0.20 // indirect
+ github.com/mattn/go-localereader v0.0.1 // indirect
+ github.com/mattn/go-runewidth v0.0.16 // indirect
+ github.com/muesli/ansi v0.0.0-20230316100256-276c6243b2f6 // indirect
+ github.com/muesli/cancelreader v0.2.2 // indirect
+ github.com/muesli/termenv v0.16.0 // indirect
+ github.com/rivo/uniseg v0.4.7 // indirect
+ github.com/xo/terminfo v0.0.0-20220910002029-abceb7e1c41e // indirect
+ golang.org/x/sys v0.36.0 // indirect
+ golang.org/x/text v0.3.8 // indirect
+)
diff --git a/go.sum b/go.sum
new file mode 100644
index 0000000..cc7a40a
--- /dev/null
+++ b/go.sum
@@ -0,0 +1,41 @@
+github.com/aymanbagabas/go-osc52/v2 v2.0.1 h1:HwpRHbFMcZLEVr42D4p7XBqjyuxQH5SMiErDT4WkJ2k=
+github.com/aymanbagabas/go-osc52/v2 v2.0.1/go.mod h1:uYgXzlJ7ZpABp8OJ+exZzJJhRNQ2ASbcXHWsFqH8hp8=
+github.com/charmbracelet/bubbletea v1.3.10 h1:otUDHWMMzQSB0Pkc87rm691KZ3SWa4KUlvF9nRvCICw=
+github.com/charmbracelet/bubbletea v1.3.10/go.mod h1:ORQfo0fk8U+po9VaNvnV95UPWA1BitP1E0N6xJPlHr4=
+github.com/charmbracelet/colorprofile v0.2.3-0.20250311203215-f60798e515dc h1:4pZI35227imm7yK2bGPcfpFEmuY1gc2YSTShr4iJBfs=
+github.com/charmbracelet/colorprofile v0.2.3-0.20250311203215-f60798e515dc/go.mod h1:X4/0JoqgTIPSFcRA/P6INZzIuyqdFY5rm8tb41s9okk=
+github.com/charmbracelet/lipgloss v1.1.0 h1:vYXsiLHVkK7fp74RkV7b2kq9+zDLoEU4MZoFqR/noCY=
+github.com/charmbracelet/lipgloss v1.1.0/go.mod h1:/6Q8FR2o+kj8rz4Dq0zQc3vYf7X+B0binUUBwA0aL30=
+github.com/charmbracelet/x/ansi v0.10.1 h1:rL3Koar5XvX0pHGfovN03f5cxLbCF2YvLeyz7D2jVDQ=
+github.com/charmbracelet/x/ansi v0.10.1/go.mod h1:3RQDQ6lDnROptfpWuUVIUG64bD2g2BgntdxH0Ya5TeE=
+github.com/charmbracelet/x/cellbuf v0.0.13-0.20250311204145-2c3ea96c31dd h1:vy0GVL4jeHEwG5YOXDmi86oYw2yuYUGqz6a8sLwg0X8=
+github.com/charmbracelet/x/cellbuf v0.0.13-0.20250311204145-2c3ea96c31dd/go.mod h1:xe0nKWGd3eJgtqZRaN9RjMtK7xUYchjzPr7q6kcvCCs=
+github.com/charmbracelet/x/term v0.2.1 h1:AQeHeLZ1OqSXhrAWpYUtZyX1T3zVxfpZuEQMIQaGIAQ=
+github.com/charmbracelet/x/term v0.2.1/go.mod h1:oQ4enTYFV7QN4m0i9mzHrViD7TQKvNEEkHUMCmsxdUg=
+github.com/erikgeiser/coninput v0.0.0-20211004153227-1c3628e74d0f h1:Y/CXytFA4m6baUTXGLOoWe4PQhGxaX0KpnayAqC48p4=
+github.com/erikgeiser/coninput v0.0.0-20211004153227-1c3628e74d0f/go.mod h1:vw97MGsxSvLiUE2X8qFplwetxpGLQrlU1Q9AUEIzCaM=
+github.com/lucasb-eyer/go-colorful v1.2.0 h1:1nnpGOrhyZZuNyfu1QjKiUICQ74+3FNCN69Aj6K7nkY=
+github.com/lucasb-eyer/go-colorful v1.2.0/go.mod h1:R4dSotOR9KMtayYi1e77YzuveK+i7ruzyGqttikkLy0=
+github.com/mattn/go-isatty v0.0.20 h1:xfD0iDuEKnDkl03q4limB+vH+GxLEtL/jb4xVJSWWEY=
+github.com/mattn/go-isatty v0.0.20/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y=
+github.com/mattn/go-localereader v0.0.1 h1:ygSAOl7ZXTx4RdPYinUpg6W99U8jWvWi9Ye2JC/oIi4=
+github.com/mattn/go-localereader v0.0.1/go.mod h1:8fBrzywKY7BI3czFoHkuzRoWE9C+EiG4R1k4Cjx5p88=
+github.com/mattn/go-runewidth v0.0.16 h1:E5ScNMtiwvlvB5paMFdw9p4kSQzbXFikJ5SQO6TULQc=
+github.com/mattn/go-runewidth v0.0.16/go.mod h1:Jdepj2loyihRzMpdS35Xk/zdY8IAYHsh153qUoGf23w=
+github.com/muesli/ansi v0.0.0-20230316100256-276c6243b2f6 h1:ZK8zHtRHOkbHy6Mmr5D264iyp3TiX5OmNcI5cIARiQI=
+github.com/muesli/ansi v0.0.0-20230316100256-276c6243b2f6/go.mod h1:CJlz5H+gyd6CUWT45Oy4q24RdLyn7Md9Vj2/ldJBSIo=
+github.com/muesli/cancelreader v0.2.2 h1:3I4Kt4BQjOR54NavqnDogx/MIoWBFa0StPA8ELUXHmA=
+github.com/muesli/cancelreader v0.2.2/go.mod h1:3XuTXfFS2VjM+HTLZY9Ak0l6eUKfijIfMUZ4EgX0QYo=
+github.com/muesli/termenv v0.16.0 h1:S5AlUN9dENB57rsbnkPyfdGuWIlkmzJjbFf0Tf5FWUc=
+github.com/muesli/termenv v0.16.0/go.mod h1:ZRfOIKPFDYQoDFF4Olj7/QJbW60Ol/kL1pU3VfY/Cnk=
+github.com/rivo/uniseg v0.2.0/go.mod h1:J6wj4VEh+S6ZtnVlnTBMWIodfgj8LQOQFoIToxlJtxc=
+github.com/rivo/uniseg v0.4.7 h1:WUdvkW8uEhrYfLC4ZzdpI2ztxP1I582+49Oc5Mq64VQ=
+github.com/rivo/uniseg v0.4.7/go.mod h1:FN3SvrM+Zdj16jyLfmOkMNblXMcoc8DfTHruCPUcx88=
+github.com/xo/terminfo v0.0.0-20220910002029-abceb7e1c41e h1:JVG44RsyaB9T2KIHavMF/ppJZNG9ZpyihvCd0w101no=
+github.com/xo/terminfo v0.0.0-20220910002029-abceb7e1c41e/go.mod h1:RbqR21r5mrJuqunuUZ/Dhy/avygyECGrLceyNeo4LiM=
+golang.org/x/sys v0.0.0-20210809222454-d867a43fc93e/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
+golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
+golang.org/x/sys v0.36.0 h1:KVRy2GtZBrk1cBYA7MKu5bEZFxQk4NIDV6RLVcC8o0k=
+golang.org/x/sys v0.36.0/go.mod h1:OgkHotnGiDImocRcuBABYBEXf8A9a87e/uXjp9XT3ks=
+golang.org/x/text v0.3.8 h1:nAL+RVCQ9uMn3vJZbV+MRnydTJFPf8qqY42YiA6MrqY=
+golang.org/x/text v0.3.8/go.mod h1:E6s5w1FMmriuDzIBO73fBruAKo1PCIq6d2Q6DHfQ8WQ=
diff --git a/index.html b/index.html
deleted file mode 100644
index 814daf4..0000000
--- a/index.html
+++ /dev/null
@@ -1,83 +0,0 @@
-
-
-
-
-
- Bitwa Morska z Zaawansowanym Botem
-
-
-
-
-
-
-
-
Wybierz rozmiar planszy
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
Panel Sterowania
-
-
-
-
-
-
Bot sugeruje strzał w pole: ...
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
Plansza Przeciwnika
-
-
-
-
-
-
-
-
AI intensywnie myśli...
-
-
-
-
-
-
diff --git a/main.go b/main.go
new file mode 100644
index 0000000..3bc0b0d
--- /dev/null
+++ b/main.go
@@ -0,0 +1,17 @@
+package main
+
+import (
+ "battleship/ui"
+ "fmt"
+ "os"
+
+ "github.com/charmbracelet/bubbletea"
+)
+
+func main() {
+ p := tea.NewProgram(ui.InitialModel(), tea.WithAltScreen())
+ if _, err := p.Run(); err != nil {
+ fmt.Printf("Alas, there's been an error: %v", err)
+ os.Exit(1)
+ }
+}
diff --git a/script.js b/script.js
deleted file mode 100644
index 74d1714..0000000
--- a/script.js
+++ /dev/null
@@ -1,2019 +0,0 @@
-// ##################################################################
-// # ETAP 2 #
-// ##################################################################
-
-const GAME_CONFIGS = {
- '10x10': {
- size: 10,
- ships: [
- { name: 'pięciomasztowiec', size: 5, count: 1 },
- { name: 'czteromasztowiec', size: 4, count: 1 },
- { name: 'trójmasztowiec', size: 3, count: 2 },
- { name: 'dwumasztowiec', size: 2, count: 1 }
- ]
- },
- '12x12': {
- size: 12,
- ships: [
- { name: 'pięciomasztowiec', size: 5, count: 1 },
- { name: 'czteromasztowiec', size: 4, count: 2 },
- { name: 'trójmasztowiec', size: 3, count: 3 },
- { name: 'dwumasztowiec', size: 2, count: 2 }
- ]
- },
- '15x15': {
- size: 15,
- ships: [
- { name: 'pięciomasztowiec', size: 5, count: 3 },
- { name: 'czteromasztowiec', size: 4, count: 3 },
- { name: 'trójmasztowiec', size: 3, count: 5 },
- { name: 'dwumasztowiec', size: 2, count: 4 }
- ]
- }
-};
-
-let currentConfig = GAME_CONFIGS['10x10'];
-
-/**
- * Tworzy planszę do gry o określonym rozmiarze wraz z koordynatami.
- * @param {string} boardId - ID elementu HTML, w którym ma być plansza.
- * @param {string} topCoordsId - ID kontenera na górne koordynaty.
- * @param {string} leftCoordsId - ID kontenera na lewe koordynaty.
- * @param {number} size - Rozmiar planszy.
- */
-function createBoard(boardId, topCoordsId, leftCoordsId, size) {
- const boardElement = document.getElementById(boardId);
- const topCoordsElement = document.getElementById(topCoordsId);
- const leftCoordsElement = document.getElementById(leftCoordsId);
-
- boardElement.innerHTML = '';
- topCoordsElement.innerHTML = '';
- leftCoordsElement.innerHTML = '';
-
- boardElement.style.setProperty('--grid-size', size);
-
- // Generowanie górnych koordynatów (cyfry)
- for (let i = 0; i < size; i++) {
- const coord = document.createElement('div');
- coord.style.width = 'var(--cell-size)';
- coord.style.textAlign = 'center';
- coord.textContent = i + 1;
- topCoordsElement.appendChild(coord);
- }
-
- // Generowanie lewych koordynatów (litery)
- for (let i = 0; i < size; i++) {
- const coord = document.createElement('div');
- coord.style.height = 'var(--cell-size)';
- coord.style.display = 'flex';
- coord.style.alignItems = 'center';
- coord.style.justifyContent = 'center';
- coord.textContent = String.fromCharCode(65 + i);
- leftCoordsElement.appendChild(coord);
- }
-
- // Generowanie siatki gry
- for (let i = 0; i < size * size; i++) {
- const cell = document.createElement('div');
- cell.classList.add('cell');
- const x = i % size;
- const y = Math.floor(i / size);
- cell.dataset.x = x;
- cell.dataset.y = y;
- boardElement.appendChild(cell);
- }
-}
-
-/**
- * Obsługuje kliknięcie komórki na planszy gracza (strzał przeciwnika).
- * @param {Event} event
- */
-function handlePlayerBoardClick(event) {
- const cell = event.target;
- if (!cell.classList.contains('cell')) return;
-
- const x = parseInt(cell.dataset.x);
- const y = parseInt(cell.dataset.y);
-
- const cellState = playerGrid[y][x];
-
- // Sprawdzenie, czy pole było już ostrzelane
- if ((typeof cellState === 'object' && cellState.hit) || cellState === 'miss') {
- alert("To pole było już ostrzelane!");
- return;
- }
-
- if (typeof cellState === 'object' && cellState !== null && !cellState.hit) {
- // Trafienie w statek
- cellState.hit = true;
- triggerAnimation(cell, 'animate-hit', 500);
- const ship = playerShips.find(s => s.id === cellState.shipId);
- ship.hits++;
-
- if (ship.hits === ship.size) {
- ship.isSunk = true;
- }
-
- if (ship.isSunk) {
- setTimeout(() => {
- ship.positions.forEach(pos => {
- const sunkCell = document.querySelector(`#player-board .cell[data-x='${pos.x}'][data-y='${pos.y}']`);
- if(sunkCell) {
- sunkCell.classList.remove('hit');
- sunkCell.classList.add('sunk');
- triggerAnimation(sunkCell, 'animate-sunk', 600);
- }
- });
- }, 550);
- }
-
- cell.classList.add('hit');
- checkWinCondition();
- } else if (cellState === 'water') {
- // Pudło
- playerGrid[y][x] = 'miss';
- triggerAnimation(cell, 'animate-miss', 400);
- cell.classList.add('miss');
- }
-}
-
-/**
- * Obsługuje kliknięcie komórki na planszy przeciwnika (symulacja strzału bota).
- * @param {Event} event
- */
-function handleOpponentBoardClick(event) {
- if (event.target.classList.contains('cell')) {
- const x = event.target.dataset.x;
- const y = event.target.dataset.y;
- document.getElementById('bot-suggestion').textContent = `${String.fromCharCode(65 + parseInt(y))}${parseInt(x) + 1}`;
- console.log(`Bot strzela w pole: (${x}, ${y})`);
- // W przyszłości stan komórki będzie zależał od odpowiedzi gracza
- }
-}
-
-
-// ##################################################################
-// # NOWY INTERFEJS #
-// ##################################################################
-
-let turnCounter = 1;
-
-
-// ##################################################################
-// # RDZEŃ AI - ŁOWCA KWANTOWY #
-// ##################################################################
-
-let allPossibleOpponentLayouts = []; // Globalna tablica przechowująca "możliwe rzeczywistości"
-
-/**
- * Pokazuje wskaźnik myślenia AI.
- */
-function showThinkingIndicator() {
- document.getElementById('ai-thinking-indicator').classList.remove('hidden');
-}
-
-/**
- * Ukrywa wskaźnik myślenia AI.
- */
-function hideThinkingIndicator() {
- document.getElementById('ai-thinking-indicator').classList.add('hidden');
-}
-
-/**
- * Inicjalizuje Łowcę Kwantowego, generując asynchronicznie początkowy zbiór możliwych układów.
- */
-function initializeQuantumHunter() {
- console.log("AI: Asynchroniczna Inicjalizacja Silnika 'Możliwych Rzeczywistości'...");
- showThinkingIndicator();
- allPossibleOpponentLayouts = [];
- const size = currentConfig.size;
- const shipsConfig = currentConfig.ships;
- const targetLayouts = 50000;
- const chunkSize = 500; // Liczba układów generowanych w jednym "kawałku" czasu
-
- function generateChunk() {
- if (allPossibleOpponentLayouts.length >= targetLayouts) {
- console.log(`AI: Wygenerowano ${allPossibleOpponentLayouts.length} możliwych rzeczywistości. Gotów do polowania.`);
- hideThinkingIndicator();
- return;
- }
-
- for (let i = 0; i < chunkSize; i++) {
- const newLayout = generateRandomValidLayout(size, shipsConfig);
- if (newLayout) {
- allPossibleOpponentLayouts.push(newLayout);
- }
- }
-
- // Zaktualizuj licznik w UI, aby pokazać postęp
- // (można dodać element UI w przyszłości)
- console.log(`Generowanie hipotez: ${allPossibleOpponentLayouts.length} / ${targetLayouts}`);
-
- // Zaplanuj następny kawałek, oddając kontrolę przeglądarce
- setTimeout(generateChunk, 0);
- }
-
- generateChunk();
-}
-
-/**
- * Generuje losowy układ statków zgodny ze znanymi ograniczeniami z opponentGrid.
- * @param {number} size - Rozmiar planszy.
- * @param {Array