Skip to content

Commit

Permalink
Improve puzzle 011, fix a few bugs (#17)
Browse files Browse the repository at this point in the history
Updated the puzzle to include a non-directional portal to further
illustrate the rules of portals. The portal mask has been updated to
select the tile the mask applies to before applying the mask (which
ensures the toolbar for that tile is selected). In addition, the
before/after edit actions on tiles have been updated to disable all
modifiers when a mask is active on that tile. The use of modifiers while
a mask was active was causing bugs, especially in the case of a portal.

This also includes a bug fix for updating beam references in a tile.
Previously, all beam references were removed from a tile when a step for
that beam was removed from a tile. However, sometimes there are multiple
steps for a single beam in a tile, so the beam reference only needs to
be removed if there are no longer any steps in that tile for the beam.

Additionally, functional tests have been updated to log to console. A bug
relating to wait conditions was also fixed, as was a bug found in puzzle
008 related to the filter item.
  • Loading branch information
kflorence authored Feb 8, 2024
1 parent 64debbc commit b55f7a9
Show file tree
Hide file tree
Showing 14 changed files with 79 additions and 41 deletions.
4 changes: 2 additions & 2 deletions .github/workflows/pull-request.yml
Original file line number Diff line number Diff line change
Expand Up @@ -8,9 +8,9 @@ jobs:
runs-on: ubuntu-latest
steps:
- name: Checkout
uses: actions/checkout@v1
uses: actions/checkout@v4
- name: Setup Node
uses: actions/setup-node@v1
uses: actions/setup-node@v4
with:
node-version: 20
- name: Install dependencies
Expand Down
4 changes: 2 additions & 2 deletions .github/workflows/release.yml
Original file line number Diff line number Diff line change
Expand Up @@ -8,9 +8,9 @@ jobs:
runs-on: ubuntu-latest
steps:
- name: Checkout
uses: actions/checkout@v1
uses: actions/checkout@v4
- name: Setup Node
uses: actions/setup-node@v1
uses: actions/setup-node@v4
with:
node-version: 20
- name: Install dependencies
Expand Down
18 changes: 9 additions & 9 deletions package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

4 changes: 2 additions & 2 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -16,11 +16,11 @@
"build": "parcel build --public-url 'https://kflorence.github.io/beaming'",
"start": "parcel",
"test": "npm run test-lint && npm run test-functional",
"test-functional": "mocha test --recursive --timeout 10000",
"test-functional": "mocha test --recursive --timeout 20000",
"test-lint": "standard"
},
"devDependencies": {
"chromedriver": "^120.0.1",
"chromedriver": "^121.0.0",
"mocha": "^10.2.0",
"parcel": "^2.9.3",
"selenium-webdriver": "^4.16.0",
Expand Down
6 changes: 3 additions & 3 deletions src/components/items/beam.js
Original file line number Diff line number Diff line change
Expand Up @@ -96,7 +96,7 @@ export class Beam extends Item {

step.index = this.#stepIndex = this.#steps.length - 1

if (!step.tile.items.some((item) => item === this)) {
if (!step.tile.items.some((item) => item.equals(this))) {
// Add this beam to the tile item list so other beams can see it
step.tile.addItem(this)
}
Expand Down Expand Up @@ -681,9 +681,9 @@ export class Beam extends Item {

console.debug(this.toString(), 'removed steps: ', deletedSteps)

// Remove beam from tiles it is being removed from
const tiles = [...new Set(deletedSteps.map((step) => step.tile))]
tiles.forEach((tile) => tile.removeItem(this))
// Remove references to the beam in any tiles it is no longer in
tiles.filter((tile) => this.getSteps(tile).length === 0).forEach((tile) => tile.removeItem(this))

deletedSteps.forEach((step) => step.onRemove(step))

Expand Down
2 changes: 1 addition & 1 deletion src/components/items/filter.js
Original file line number Diff line number Diff line change
Expand Up @@ -34,7 +34,7 @@ export class Filter extends movable(Item) {
return [getColorElement(this.color)]
}

onCollision (beam, puzzle, { currentStep, nextStep }) {
onCollision ({ currentStep, nextStep }) {
// The beam will collide with the filter twice, on entry and exit, so ignore the first one, but track in state
return nextStep.copy(
currentStep.state.has(StepState.Filter)
Expand Down
1 change: 1 addition & 0 deletions src/components/items/portal.js
Original file line number Diff line number Diff line change
Expand Up @@ -161,6 +161,7 @@ export class Portal extends movable(rotatable(Item)) {
}
)

puzzle.updateSelectedTile(currentStep.tile)
puzzle.mask(mask)

// This will cause the beam to stop
Expand Down
2 changes: 2 additions & 0 deletions src/components/items/tile.js
Original file line number Diff line number Diff line change
Expand Up @@ -46,11 +46,13 @@ export class Tile extends Item {

afterModify () {
this.setStyle(this.selected ? 'selected' : 'default')
this.modifiers.forEach((modifier) => modifier.update({ disabled: false }))
}

beforeModify () {
this.group.bringToFront()
this.setStyle('edit')
this.modifiers.forEach((modifier) => modifier.update({ disabled: true }))
}

getState () {
Expand Down
15 changes: 10 additions & 5 deletions src/components/modifier.js
Original file line number Diff line number Diff line change
Expand Up @@ -185,15 +185,20 @@ export class Modifier extends Stateful {
options || {}
)

this.disabled = options.disabled
if (!this.immutable) {
this.disabled = options.disabled
}

this.name = options.name
this.title = options.title
this.selected = options.selected

this.#container.classList.toggle('disabled', this.disabled)
this.#container.classList.toggle('selected', this.selected)
this.element.textContent = this.name
this.element.title = this.title
if (this.#container) {
this.#container.classList.toggle('disabled', this.disabled)
this.#container.classList.toggle('selected', this.selected)
this.element.textContent = this.name
this.element.title = this.title
}
}

#maskOnTap (puzzle, tile) {
Expand Down
4 changes: 4 additions & 0 deletions src/components/puzzle.js
Original file line number Diff line number Diff line change
Expand Up @@ -156,6 +156,8 @@ export class Puzzle {
}

mask.onMask(this)

document.body.classList.add(Puzzle.Events.Mask)
}

select (id) {
Expand All @@ -179,6 +181,8 @@ export class Puzzle {
this.#mask.onUnmask(this)
this.#mask = undefined

document.body.classList.remove(Puzzle.Events.Mask)

const mask = this.#maskQueue.pop()
if (mask) {
// Evaluate after any current events have processed (e.g. beam updates from last mask)
Expand Down
1 change: 1 addition & 0 deletions src/components/state.js
Original file line number Diff line number Diff line change
Expand Up @@ -95,6 +95,7 @@ export class State {
this.#current = structuredClone(this.#original)
this.#deltas = []
this.#index = this.#lastIndex()
this.#selectedTile = undefined

State.clearCache(this.getId())

Expand Down
10 changes: 5 additions & 5 deletions src/puzzles/011.js
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,6 @@ export default {
{
items: [
{
direction: 5,
type: 'Portal'
}
],
Expand Down Expand Up @@ -50,12 +49,12 @@ export default {
{
items: [
{
direction: 2,
direction: 0,
type: 'Portal'
}
],
modifiers: [
{ type: 'Lock' }
{ type: 'Rotate' }
],
type: 'Tile'
},
Expand Down Expand Up @@ -88,7 +87,7 @@ export default {
}
],
modifiers: [
{ type: 'Rotate' }
{ type: 'Lock' }
],
type: 'Tile'
},
Expand All @@ -110,5 +109,6 @@ export default {
},
solution: [
{ amount: 1, type: 'Connections' }
]
],
version: 1
}
37 changes: 30 additions & 7 deletions test/fixtures.js
Original file line number Diff line number Diff line change
@@ -1,6 +1,11 @@
require('chromedriver')
const chrome = require('selenium-webdriver/chrome')
const { Builder, By, until, WebElementCondition } = require('selenium-webdriver')
const { Builder, By, Condition, logging, until } = require('selenium-webdriver')

logging.installConsoleHandler()

const logger = logging.getLogger('')
logger.setLevel(logging.Level.DEBUG)

class PuzzleFixture {
driver
Expand Down Expand Up @@ -28,13 +33,17 @@ class PuzzleFixture {
'--disable-dev-shm-usage',
'--disable-extensions',
'--disable-gpu',
'--headless=new',
'--headless',
'--ignore-certificate-errors',
'--no-sandbox',
'--window-size=768,1024'
)

console.log('Building driver...')
this.driver = await new Builder().forBrowser('chrome').setChromeOptions(options).build()
this.driver = await new Builder()
.forBrowser('chrome')
.setChromeOptions(options)
.build()

console.log(`Getting URL: ${this.url}`)
await this.driver.get(this.url)
Expand All @@ -60,13 +69,21 @@ class PuzzleFixture {
await this.driver.actions({ async: true }).move({ origin: this.elements.canvas }).click().perform()
}

async isMasked () {
return this.driver.wait(untilElementHasClass(this.elements.body, 'puzzle-mask'))
}

async isNotMasked () {
return this.driver.wait(untilElementDoesNotHaveClass(this.elements.body, 'puzzle-mask'))
}

async isSolved () {
return elementHasClass(this.elements.body, 'puzzle-solved')
return this.driver.wait(untilElementHasClass(this.elements.body, 'puzzle-solved'))
}

async selectModifier (name) {
const origin = this.#getModifier(name)
await this.driver.actions({ async: true }).move({ origin }).press().pause(500).release().perform()
await this.driver.actions({ async: true }).move({ origin }).press().pause(501).release().perform()
}

async #getModifier (name) {
Expand All @@ -77,12 +94,18 @@ class PuzzleFixture {
static baseUrl = 'http://localhost:1234'
}

function elementHasClass (element, name) {
return new WebElementCondition('until element has class', function () {
function untilElementHasClass (element, name) {
return new Condition('until element has class', function () {
return element.getAttribute('class').then((classes) => classes.split(' ').some((className) => name === className))
})
}

function untilElementDoesNotHaveClass (element, name) {
return new Condition('until element does not have class', function () {
return element.getAttribute('class').then((classes) => classes.split(' ').every((className) => name !== className))
})
}

module.exports = {
PuzzleFixture
}
12 changes: 7 additions & 5 deletions test/puzzles/011.js
Original file line number Diff line number Diff line change
Expand Up @@ -9,13 +9,15 @@ describe('Puzzle 011', function () {
before(puzzle.before)

it('should be solved', async function () {
await puzzle.clickTile(2, 0)
await puzzle.clickModifier('rotate', { times: 3 })
await puzzle.clickTile(0, 1)
await puzzle.clickTile(1, 1)
await puzzle.clickModifier('rotate', { times: 2 })
await puzzle.isMasked()
await puzzle.clickTile(2, 1)
await puzzle.isNotMasked()

await puzzle.clickTile(2, 0)
await puzzle.clickTile(1, 1)
await puzzle.selectModifier('rotate')
await puzzle.clickTile(0, 0)
await puzzle.clickTile(2, 0)
await puzzle.clickModifier('rotate', { times: 3 })

assert(await puzzle.isSolved())
Expand Down

0 comments on commit b55f7a9

Please sign in to comment.