Skip to content

Commit

Permalink
Auto solver (#12)
Browse files Browse the repository at this point in the history
* added testing for pinned ships

* incomplete but need to sync

* added psuedo-code for constructor + jsdoc

* added directional ship checks and added logic psuedo-code

* added uni and bi directional 'solving'

* added full row/column 'solving'

* horizontal and vertical types are not internal, they are graphical
removed INTERNAL_TYPES and fixed all problems associated

* added more solving and tested existing solving
some minor errors, specifically for rows/columns

* added rudimentary images for ships
computeGraphicalTypes() needs to be debugged and solve() rebuilt from scratch.
gosh freaking darn it

* fixed compute graphical types returning early
caused all squares after a pinned square to not be computed

* fixed copy function and added testing

* added check for full rows/cols
and for rows/cols that would be full if all unkowns were ships

* somewhat fixed solving
FINALLY LET'S FREAKING GO

* fixed BoardBuilder.copy() mutating the original board

* fixed various issues with orthogonal ships. solving now seems to work as expected

* added functions to count vertical and horizontal runs with testing

* fixed spelling error

* updated todo

* added getRuns and countRunsLeft functions
with testing

* Update jest.yml

* solving is finally complete

---------

Co-authored-by: rpschedule <103065303+rpschedule@users.noreply.github.com>
  • Loading branch information
lGrom and rpschedule committed Jul 26, 2024
1 parent 03a5125 commit 938f576
Show file tree
Hide file tree
Showing 18 changed files with 4,207 additions and 2,659 deletions.
4 changes: 2 additions & 2 deletions .github/workflows/jest.yml
Original file line number Diff line number Diff line change
Expand Up @@ -5,9 +5,9 @@ name: Unit tests

on:
push:
branches: [ "master" ]
branches: [ "autoSolver" ]
pull_request:
branches: [ "master" ]
branches: [ "autoSolver" ]

jobs:
build:
Expand Down
3 changes: 3 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
@@ -1,5 +1,8 @@
# See https://help.github.com/articles/ignoring-files/ for more about ignoring files.

# vscode things
/.vscode

# dependencies
/node_modules
/.pnp
Expand Down
17 changes: 16 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
# Battleship solitaire

[![CI - tests](https://github.com/lgrom/battleship-solitare/actions/workflows/jest.yml/badge.svg)](https://github.com/lgrom/battleship-solitare/actions/workflows/jest.yml)

TODO:
Expand All @@ -7,8 +8,22 @@ TODO:
- [X] add support for pre-existing ships
- [X] make setShip accept playtypes as well as ship objects
- [X] make graphical types auto-compute
- [ ] add testing for pinned ships
- [X] add testing for pinned ships
- [ ] make an automatic solver
- [ ] make the styling automatically adjust for the width and heigh of the board
- [ ] make a solvable board generator with with options for difficulty and guess and check
- [ ] add styling to webpage
- [ ] make jsdoc more consistent (eg. dashes after param names, capitalization)
- [ ] add dev documentation

more specific things
- [ ] make position arrays start at 0 instead of 1
- [ ] name "uni/bi-directional" variables as "cardinal" and "orthognol" (or however it's spelled)
- [ ] store runs as 2 dimensional arrays where inner arrays = all indexes of squares in that run instead of just storing the start, end, and length of each run.
- [ ] add testing for execution time
- [ ] for the countHorizontal/VerticalRuns functions you could update them to use indexes instead of position arrays. then instead of having an array of objects with length, start, and end properties, you just get the array's length, the first index, or the last index *

to finish solving:
- [X] function to check for runs
- [X] function to update shipsLeft, maybe memoized in the future
- [ ] check where the longest ship could fit, then re-run the
5,798 changes: 3,301 additions & 2,497 deletions package-lock.json

Large diffs are not rendered by default.

6 changes: 6 additions & 0 deletions public/ships/end.svg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
4 changes: 4 additions & 0 deletions public/ships/ship.svg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
3 changes: 3 additions & 0 deletions public/ships/single.svg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
3 changes: 3 additions & 0 deletions public/ships/vertical-horizontal.svg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
4 changes: 4 additions & 0 deletions public/ships/water.svg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
4 changes: 2 additions & 2 deletions src/App.css
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
body {
background-color: black;
color: white;
background-color: rgb(246, 246, 255);
color: black;
}
57 changes: 54 additions & 3 deletions src/App.js
Original file line number Diff line number Diff line change
Expand Up @@ -3,15 +3,66 @@ import React from 'react';
import './App.css';
import Board from './Board/Board';
import BoardBuilder from './Board/BoardBuilder';
import Ship, { PLAY_TYPES } from './Board/Ship';
import Ship, { GRAPHICAL_TYPES, PLAY_TYPES } from './Board/Ship';

const preset = new BoardBuilder(4, 4).setShip([1, 3], PLAY_TYPES.SHIP).setShip([3, 1], PLAY_TYPES.WATER);
// https://www.brainbashers.com/showbattleships.asp?date=0227&size=6&puzz=A
// const preset = new BoardBuilder(6, 6)
// .setShip([1, 6], PLAY_TYPES.WATER, true)
// .setShip([5, 4], GRAPHICAL_TYPES.SINGLE, true);

// columnCounts={[1, 1, 4, 0, 4, 0]} rowCounts={[2, 2, 2, 1, 0, 3]

// const preset = new BoardBuilder(12, 12).setShip(3, 1, false).setShip(9, 6, false).setShip(10, 9, false).setShip(13, 2, false).setShip(23, 1, false).setShip(27, 1, false).setShip(31, 1, false).setShip(68, 1, false).setShip(72, 1, false).setShip(73, 1, false).setShip(74, 1, false).setShip(84, 1, false).setShip(85, 3, false).setShip(86, 1, false).setShip(90, 1, false).setShip(91, 5, false).setShip(92, 9, false).setShip(94, 1, false).setShip(95, 1, false).setShip(96, 1, false).setShip(97, 1, false).setShip(98, 1, false).setShip(106, 1, false).setShip(107, 3, false).setShip(118, 1, false).setShip(119, 1, false).setShip(126, 1, false).setShip(127, 5, false).setShip(128, 9, false);
// columnCounts={[0, 3, 1, 3, 1, 1, 5, 2, 2, 3, 3, 3]} rowCounts={[2, 5, 1, 5, 1, 1, 3, 4, 1, 0, 3, 1]}

// https://www.brainbashers.com/showbattleships.asp?date=0607&size=15&puzz=A
const preset = new BoardBuilder(15, 15)
.setShip([4, 1], GRAPHICAL_TYPES.SHIP, true)
.setShip([7, 1], GRAPHICAL_TYPES.WATER, true)
.setShip([2, 3], GRAPHICAL_TYPES.WATER, true)
.setShip([13, 3], GRAPHICAL_TYPES.SINGLE, true)
.setShip([3, 4], GRAPHICAL_TYPES.LEFT, true)
.setShip([6, 4], GRAPHICAL_TYPES.WATER, true)
.setShip([11, 4], GRAPHICAL_TYPES.SHIP, true)
.setShip([11, 5], GRAPHICAL_TYPES.VERTICAL, true)
.setShip([13, 5], GRAPHICAL_TYPES.SINGLE, true)
.setShip([3, 6], GRAPHICAL_TYPES.DOWN, true)
.setShip([7, 7], GRAPHICAL_TYPES.SINGLE, true)
.setShip([8, 7], GRAPHICAL_TYPES.WATER, true)
.setShip([13, 7], GRAPHICAL_TYPES.WATER, true)
.setShip([7, 8], GRAPHICAL_TYPES.WATER, true)
.setShip([11, 8], GRAPHICAL_TYPES.WATER, true)
.setShip([13, 8], GRAPHICAL_TYPES.WATER, true)
.setShip([5, 9], GRAPHICAL_TYPES.SHIP, true)
.setShip([10, 9], GRAPHICAL_TYPES.WATER, true)
.setShip([3, 11], GRAPHICAL_TYPES.WATER, true)
.setShip([7, 12], GRAPHICAL_TYPES.SHIP, true)
.setShip([10, 12], GRAPHICAL_TYPES.SHIP, true)
.setShip([15, 12], GRAPHICAL_TYPES.WATER, true)
.setShip([3, 13], GRAPHICAL_TYPES.WATER, true)
.setShip([4, 13], GRAPHICAL_TYPES.WATER, true)
.setShip([8, 13], GRAPHICAL_TYPES.WATER, true)
.setShip([10, 13], GRAPHICAL_TYPES.SHIP, true)
.setShip([2, 14], GRAPHICAL_TYPES.UP, true)
.setShip([7, 14], GRAPHICAL_TYPES.WATER, true)
.setShip([4, 15], GRAPHICAL_TYPES.WATER, true)
.setShip([15, 15], GRAPHICAL_TYPES.SINGLE, true)
.computeGraphicalTypes()
// .setShip(202, GRAPHICAL_TYPES.SINGLE, true)
;

class App extends React.Component {
render () {
return (
<div className="App">
<Board preset={preset} />
<Board
preset={preset}
width={15}
height={15}
columnCounts={[0, 5, 6, 1, 5, 1, 5, 1, 0, 5, 3, 0, 2, 0, 1]}
rowCounts={[2, 0, 1, 3, 4, 2, 3, 2, 4, 0, 4, 3, 3, 2, 2]}
runs={[5, 4, 3, 2, 1]}
/>
</div>
);
}
Expand Down
11 changes: 9 additions & 2 deletions src/Board/Board.css
Original file line number Diff line number Diff line change
@@ -1,12 +1,19 @@
.Board {
display: grid;
grid-template: repeat(4, 100px) / repeat(4, 100px);
grid-template: repeat(15, 50px) / repeat(15, 50px);
}

.Square {
border: 1px solid white;
border: 1px solid gray;
display: flex;
justify-content: center;
align-items: center;
}

.Square:hover {
cursor: pointer;
}

.svg-path {
fill: white;
}
67 changes: 55 additions & 12 deletions src/Board/Board.js
Original file line number Diff line number Diff line change
@@ -1,27 +1,38 @@
import React from 'react';
import './Board.css';
import BoardBuilder from './BoardBuilder';
import { GRAPHICAL_TYPES } from './Ship';

/**
* The board component and all its parts
* @param {Number} width
* @param {Number} height
* @param {BoardBuilder} preset - pre-existing ships
* The visible board
* @param {Number} width Width in squares
* @param {Number} height Height in squares
* @param {BoardBuilder} [preset] Pre-existing ships
* @param {BoardBuilder} [solution] Ending board (leave undefined if using vert/hoz count and shipsLeft)
* @param {Number[]} [columnCounts] Number of ships in each column (left to right)
* @param {Number[]} [rowCounts] Number of ships in each row (top to bottom)
* @param {Number[]} [shipsLeft] Number of each type of ship left (eg. 3 solos and 1 double = [3, 1])
*/
export default class Board extends React.Component {
constructor (props) {
super(props);

// ill fidle with the react stuff later. for now, I'll add pre-existing ships to BoardBuiler
this.state = {
board: new BoardBuilder(this.props.width, this.props.height, this.props.preset)
board: new BoardBuilder(this.props.width, this.props.height, this.props.preset, undefined, this.props.columnCounts, this.props.rowCounts, this.props.runs)
};
}

solveBoard () {
this.setState({ board: BoardBuilder.solve(this.state.board) });
}

handleClick (event, index) {
const ship = this.state.board.getShip(index);

if (ship.pinned) return;

if (event.button === 0 || event.button === 2) {
const ship = this.state.board.getShip(index);
// this makes it +1 for left click and +2 for right click (which basically works as -1)
// this makes it +1 for left click and +2 for right click (which basically works as -1, but without making it negative)
const newType = (ship.playType + 1 + event.button / 2) % 3;
ship.setPlayType(newType);
this.setState({ board: this.state.board.setShip(index, ship) });
Expand All @@ -30,6 +41,33 @@ export default class Board extends React.Component {
this.state.board.computeGraphicalTypes();
}

svgObjectFromType (type) {
switch (type) {
case GRAPHICAL_TYPES.SINGLE:
return <object type="image/svg+xml" data="./ships/single.svg">Up</object>;
case GRAPHICAL_TYPES.UP:
return <object type="image/svg+xml" data="./ships/end.svg">Up</object>;
case GRAPHICAL_TYPES.RIGHT:
return <object type="image/svg+xml" data="./ships/end.svg">Right</object>;
case GRAPHICAL_TYPES.LEFT:
return <object type="image/svg+xml" data="./ships/end.svg">Left</object>;
case GRAPHICAL_TYPES.DOWN:
return <object type="image/svg+xml" data="./ships/end.svg">Down</object>;
case GRAPHICAL_TYPES.SHIP:
return <object type="image/svg+xml" data="./ships/ship.svg">Ship</object>;
case GRAPHICAL_TYPES.HORIZONTAL:
return <object type="image/svg+xml" data="./ships/vertical-horizontal.svg">Vertical/Horizontal</object>;
case GRAPHICAL_TYPES.VERTICAL:
return <object type="image/svg+xml" data="./ships/vertical-horizontal.svg">Vertical/Horizontal</object>;
case GRAPHICAL_TYPES.ORTHOGONAL:
return <object type="image/svg+xml" data="./ships/vertical-horizontal.svg">Vertical/Horizontal</object>;
case GRAPHICAL_TYPES.WATER:
return <object type="image/svg+xml" data="./ships/water.svg">Water</object>;
default:
return <object/>;
}
}

displayBoard () {
return this.state.board.boardState.map((ship, index) => {
return <div
Expand All @@ -38,16 +76,21 @@ export default class Board extends React.Component {
onMouseUp={(event) => this.handleClick(event, index)}
onContextMenu={(e) => e.preventDefault()}
>
{ship.toString()}
{this.svgObjectFromType(ship.graphicalType)}
</div>;
});
}

render () {
return (
<div className="Board">
{this.displayBoard()}
</div>
<>
<div className="Board">
{this.displayBoard()}
</div>
<button onClick={() => { this.solveBoard(); }}>
Solve
</button>
</>
);
}
}
Loading

0 comments on commit 938f576

Please sign in to comment.