diff --git a/site.css b/site.css index 631e9d1..b07634d 100644 --- a/site.css +++ b/site.css @@ -43,6 +43,8 @@ main { right: 10vw; transform: perspective(50cm) rotateX(45deg); + + transition: transform 2s ease; } .Cup { @@ -96,8 +98,11 @@ main { .Die { background: #fff; border: 1px solid #000; - height: 20px; + border-radius: 2px; + height: 10px; position: fixed; - transform: translate(-10px, -10px); - width: 20px; + transform: translate(-5px, -5px); + width: 10px; + + transition: transform 0.4s ease-out; } diff --git a/src/die.ts b/src/die.ts index 9926705..5b83024 100644 --- a/src/die.ts +++ b/src/die.ts @@ -1,4 +1,6 @@ import { Power } from "./power.js"; +import { View } from "./model.js"; +import { MessageHandler } from "./updates.js"; interface Point { x: number; @@ -19,6 +21,7 @@ const touchLocation = (event: TouchEvent): Point => { export class Die { node: HTMLDivElement; + messageHandler: MessageHandler; power: Power; x: number; @@ -32,7 +35,7 @@ export class Die { positionRAF: number | null = null; - constructor() { + constructor(messageHandler: MessageHandler) { const node = document.getElementById("Die"); if (!node) { throw new Error("No dice"); @@ -40,6 +43,7 @@ export class Die { this.node = node as HTMLDivElement; + this.messageHandler = messageHandler; this.power = new Power(); this.x = this.initialX(); @@ -96,7 +100,10 @@ export class Die { window.removeEventListener("mouseup", this.dragEnd); window.removeEventListener("touchend", this.dragEnd); - console.log("TOSS!", this.startX, this.startY, this.x, this.y); + this.messageHandler.update("toss", { + power: this.getTossPower(), + direction: this.getTossDirection() + }); } private updatePosition = () => { @@ -119,4 +126,15 @@ export class Die { return 0; } } + + private getTossDirection = () => { + if (this.startX && this.startY) { + const dx = this.x - this.startX; + const dy = this.y - this.startY; + + return Math.atan2(dy, dx); + } else { + return 0; + } + } } diff --git a/src/index.ts b/src/index.ts index 7bd8bde..2f796aa 100644 --- a/src/index.ts +++ b/src/index.ts @@ -1,5 +1,7 @@ import { Die } from "./die.js"; +import { MessageHandler } from "./updates.js"; export const startApp = () => { - const die = new Die(); + const messageHandler = new MessageHandler(); + const die = new Die(messageHandler); }; diff --git a/src/model.ts b/src/model.ts new file mode 100644 index 0000000..c3817ca --- /dev/null +++ b/src/model.ts @@ -0,0 +1,9 @@ +export interface State { + view: View; +} + +export enum View { + Throw, + Toss, + Wait +} diff --git a/src/updates.ts b/src/updates.ts new file mode 100644 index 0000000..fbb3079 --- /dev/null +++ b/src/updates.ts @@ -0,0 +1,102 @@ +import * as Model from "./model.js"; + + +// Updates +const toss = ({ power, direction }: { power: number, direction: number }) => { + console.log("Power", power); + console.log("Direction", direction); + + document.getElementById("Table")!.style.transform = "initial"; + document.getElementById("Toss")!.style.display = "none"; + document.getElementById("Die")!.style.display = "none"; + + const die = document.createElement("div"); + + const startX = window.outerWidth / 2; + const startY = window.outerHeight * 0.9; + + die.id = "Die-toss"; + die.className = "Die"; + die.style.left = startX + "px"; + die.style.top = startY + "px"; + + document.getElementById("game")!.appendChild(die); + + // Add +/- pi/20 variance + const throwDirection = direction + Math.random()*0.15 - 0.075; + + const table = document.getElementById("Table"); + if (!table) { + throw new Error("no table"); + } + + const tableX = table.clientLeft; + const tableY = table.clientTop; + const tableWidth = table.clientWidth; + const tableHeight = table.clientHeight; + + // multiplying both by tableHeight as we want at full-power throw to go off + // the far side of the table + const dx = tableHeight * (0.3 + power) * Math.cos(throwDirection); + const dy = tableHeight * (0.3 + power) * Math.sin(throwDirection); + + console.log("Moving to", dx, dy); + + setTimeout(() => { + die.style.transform = `translate(${dx}px, ${dy}px)` + }, 2000); +}; + +const setView = (view: Model.View) => { + console.log("Load view", view); + switch (view) { + case Model.View.Throw: + const toss = document.getElementById("Toss") + toss!.style.visibility = "none"; + return; + case Model.View.Toss: + case Model.View.Wait: + return; + } +}; + +type Updates = typeof updates; +type UpdateFn = keyof Updates; + +const updates = { + setView, + toss +}; + +type Func = F extends (a: infer A) => any ? A : never; +type Message = { + fn: F, + args: Func +}; + +export class MessageHandler { + constructor() { + window.addEventListener("message", this.recieve); + } + + update = (fn: F, args: Func) => { + const message = JSON.stringify({ fn, args }); + window.postMessage(message, window.location.origin); + } + + recieve = (event: MessageEvent) => { + if (event.origin !== window.location.origin) return; + const message: Message = extractData(event.data); + + (updates[message.fn])(message.args) + } +} + +const extractData = (data: string): Message => { + const message = JSON.parse(data); + if (!message.fn) { + throw new Error("invalid message"); + } + + return message; +}