Skip to content

Commit

Permalink
snooker
Browse files Browse the repository at this point in the history
  • Loading branch information
tailuge committed Oct 23, 2023
1 parent 18340ee commit 6bce050
Show file tree
Hide file tree
Showing 7 changed files with 168 additions and 18 deletions.
4 changes: 2 additions & 2 deletions dist/diagram.js

Large diffs are not rendered by default.

4 changes: 2 additions & 2 deletions dist/index.js

Large diffs are not rendered by default.

16 changes: 10 additions & 6 deletions src/controller/rules/rulefactory.ts
Original file line number Diff line number Diff line change
@@ -1,15 +1,19 @@
import { FourteenOne } from "./fourteenone"
import { NineBall } from "./nineball"
import { Snooker } from "./snooker"
import { ThreeCushion } from "./threecushion"

export class RuleFactory {
static create(ruletype, container) {
if (ruletype === "threecushion") {
return new ThreeCushion(container)
switch (ruletype) {
case "threecushion":
return new ThreeCushion(container)
case "fourteenone":
return new FourteenOne(container)
case "snooker":
return new Snooker(container)
default:
return new NineBall(container)
}
if (ruletype === "fourteenone") {
return new FourteenOne(container)
}
return new NineBall(container)
}
}
71 changes: 71 additions & 0 deletions src/controller/rules/snooker.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,71 @@
import { WatchEvent } from "../../events/watchevent"
import { Ball, State } from "../../model/ball"
import { Outcome, OutcomeType } from "../../model/outcome"
import { R } from "../../model/physics/constants"
import { Table } from "../../model/table"
import { Rack } from "../../utils/rack"
import { TableGeometry } from "../../view/tablegeometry"
import { Controller } from "../controller"
import { NineBall } from "./nineball"
import { Rules } from "./rules"

export class Snooker extends NineBall implements Rules {
override rack() {
return Rack.snooker()
}

override update(outcome: Outcome[]): Controller {
const table = this.container.table
if (Outcome.potCount(outcome) > 0) {
if (
this.redsOnTable(table) ||
Outcome.isCueBallPotted(table.cueball, outcome)
) {
if (this.respotAllPottedColours(outcome)) {
const state = table.serialise()
const rerack = new WatchEvent({ ...state, rerack: true })
this.container.sendEvent(rerack)
this.container.recoder.record(rerack)
}
}
}
return super.update(outcome)
}

redsOnTable(table: Table) {
return table.balls.filter((b, i) => i > 6 && b.onTable()).length > 0
}

respotAllPottedColours(outcome: Outcome[]) {
return outcome
.filter((o) => o.type === OutcomeType.Pot)
.map((o) => o.ballA!)
.filter((ball) => ball.id < 7)
.filter((ball) => ball.id !== 0)
.map((ball) => this.respot(ball))
.some((e) => e === true)
}

respot(ball: Ball) {
const positions = Rack.snookerColourPositions()
positions.push(positions[ball.id - 1])
positions.reverse()
// add positions as close as possible to spot behind, then infront
let pos = positions[0].clone()
for (let x = pos.x; x < TableGeometry.tableX; x += R / 4) {
positions.push(pos.setX(x).clone())
}
pos = positions[0].clone()
for (let x = pos.x; x > -TableGeometry.tableX; x -= R / 4) {
positions.push(pos.setX(x).clone())
}
return positions.some((p) => {
if (!this.container.table.overlapsAny(p, ball)) {
ball.pos.copy(p)
ball.state = State.Stationary
return true
}
return false
})
}
}
52 changes: 45 additions & 7 deletions src/utils/rack.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import { Ball, State } from "../model/ball"
import { TableGeometry } from "../view/tablegeometry"
import { Vector3 } from "three"
import { round, roundVec, vec } from "./utils"
import { roundVec, vec } from "./utils"
import { R } from "../model/physics/constants"
import { Table } from "../model/table"

Expand All @@ -11,6 +11,7 @@ export class Rack {
static readonly up = new Vector3(0, 0, -1)
static readonly spot = new Vector3(-TableGeometry.X / 2, 0.0, 0)
static readonly across = new Vector3(0, Rack.gap, 0)
static readonly down = new Vector3(Rack.gap, 0, 0)
static readonly diagonal = Rack.across
.clone()
.applyAxisAngle(Rack.up, (Math.PI * 1) / 3)
Expand Down Expand Up @@ -59,14 +60,15 @@ export class Rack {

static triangle() {
const tp = Rack.trianglePositions()
const cueball = Rack.cueBall(Rack.spot)
const triangle = tp.map((p) => new Ball(Rack.jitter(p)))
triangle.unshift(Rack.cueBall(Rack.spot))
return triangle
triangle.unshift(cueball)
return triangle.slice(0, 5)
}

static trianglePositions() {
const triangle: Vector3[] = []
const pos = new Vector3(TableGeometry.tableX / 2, 0, 0)
const pos = new Vector3(TableGeometry.X / 2, 0, 0)
triangle.push(vec(pos))
// row 2
pos.add(this.diagonal)
Expand Down Expand Up @@ -101,7 +103,7 @@ export class Rack {
pos.add(this.across)
triangle.push(vec(pos))

return triangle.slice(0, 4)
return triangle
}

static rerack(key: Ball, table: Table) {
Expand All @@ -124,11 +126,47 @@ export class Rack {

static three() {
const threeballs: Ball[] = []
const dx = round(TableGeometry.X / 2)
const dy = round(TableGeometry.Y / 4)
const dx = TableGeometry.X / 2
const dy = TableGeometry.Y / 4
threeballs.push(Rack.cueBall(Rack.jitter(new Vector3(-dx, -dy, 0))))
threeballs.push(new Ball(Rack.jitter(new Vector3(-dx, 0, 0)), 0xe0de36))
threeballs.push(new Ball(Rack.jitter(new Vector3(dx, 0, 0)), 0xff0000))
return threeballs
}

static readonly sixth = (TableGeometry.Y * 2) / 6
static readonly baulk = (-1.5 * TableGeometry.X * 2) / 5

static snooker() {
const balls: Ball[] = []
const dy = TableGeometry.Y / 4
balls.push(Rack.cueBall(Rack.jitter(new Vector3(Rack.baulk, -dy / 2, 0))))

const colours = Rack.snookerColourPositions()
balls.push(new Ball(Rack.jitter(colours[0]), 0xe0de36))
balls.push(new Ball(Rack.jitter(colours[1]), 0x006666))
balls.push(new Ball(Rack.jitter(colours[2]), 0x773300))
balls.push(new Ball(Rack.jitter(colours[3]), 0x2222dd))
balls.push(new Ball(Rack.jitter(colours[4]), 0xff77aa))
balls.push(new Ball(Rack.jitter(colours[5]), 0x010101))

const triangle = Rack.trianglePositions().slice(0, 6)
triangle.forEach((p) => {
balls.push(new Ball(Rack.jitter(p.add(Rack.down)), 0xcc0000))
})
return balls
}

static snookerColourPositions() {
const dx = TableGeometry.X / 2
const black = TableGeometry.X - (TableGeometry.X * 2) / 11
const positions: Vector3[] = []
positions.push(new Vector3(Rack.baulk, -Rack.sixth, 0))
positions.push(new Vector3(Rack.baulk, Rack.sixth, 0))
positions.push(new Vector3(Rack.baulk, 0, 0))
positions.push(new Vector3(0, 0, 0))
positions.push(new Vector3(dx, 0, 0))
positions.push(new Vector3(black, 0, 0))
return positions
}
}
4 changes: 3 additions & 1 deletion src/view/aiminputs.ts
Original file line number Diff line number Diff line change
Expand Up @@ -35,7 +35,9 @@ export class AimInputs {
})
this.cueHitElement?.addEventListener("click", this.hit)
this.cuePowerElement?.addEventListener("change", this.powerChanged)
document.addEventListener("dblclick", this.hit)
if (!("ontouchstart" in window)) {
document.addEventListener("dblclick", this.hit)
}
document.addEventListener("wheel", this.mousewheel)
}

Expand Down
35 changes: 35 additions & 0 deletions test/rules/snooker.spec.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
import "mocha"
import { expect } from "chai"
import { Container } from "../../src/container/container"
import { GameEvent } from "../../src/events/gameevent"
import { initDom } from "../view/dom"

initDom()

const jestConsole = console

beforeEach(() => {
global.console = require("console")
})

afterEach(() => {
global.console = jestConsole
})

describe("Snooker", () => {
let container: Container
let broadcastEvents: GameEvent[]
const rule = "snooker"

beforeEach(function (done) {
container = new Container(undefined, (_) => {}, false, rule)
broadcastEvents = []
container.broadcast = (x) => broadcastEvents.push(x)
done()
})

it("Fourteenone has 13 balls", (done) => {
expect(container.table.balls).to.be.length(13)
done()
})
})

0 comments on commit 6bce050

Please sign in to comment.