class Rummikub extends Croquet.Model { init() { this.tiles = ['red', 'blue', 'green', 'orange'].map(color => [1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12].map(number => ({ color, number, tileState: 'UNPICKED', x: Math.random() * .2, y: Math.random() * .2 }))).reduce((a, b) => a.concat(b), [{ color: 'green', number: ':)', tileState: 'UNPICKED', x: Math.random() * .2, y: Math.random() * .2 }, { color: 'red', number: ':)', tileState: 'UNPICKED', x: Math.random() * .2, y: Math.random() * .2 }]) this.users = [] this.stillOpen = true this.subscribe("default", "drawTile", this.drawTile) this.subscribe("default", "newUser", this.newUser) this.subscribe("default", "updateTile", this.updateTile) } unpickedTiles() { return this.tiles.filter(tile => tile.tileState === 'UNPICKED') } newUser(user) { this.users.push(user) } drawTile(name) { if (this.unpickedTiles().length) { let tile = this.unpickedTiles()[Math.floor(Math.random() * this.unpickedTiles().length)] tile.tileState = name return tile } } updateTile({number, color, tileState, x, y}) { let tile = this.tiles.find(tile => tile.number === number && tile.color === color) if (tileState) tile.tileState = tileState if (x) tile.x = x if (y) tile.y = y } } Rummikub.register(); function inHand(y) { return y > hand.getBoundingClientRect().top } class MyView extends Croquet.View { constructor(model) { super(model); this.model = model; let savedName = localStorage.getItem('name'); if (savedName !== null && savedName !== "null") { this.name = savedName } else { this.name = prompt("What's your name?") localStorage.setItem('name', this.name) } this.publish("default", "newUser", {name: this.name, viewId: this.viewId}) } getTile({color, number}) { return this.model.tiles.find(tile => tile.number === number && tile.color === color) } update() { let score = {} this.model.tiles.forEach(({tileState}) => { if (!(tileState === "UNPICKED" || tileState === "PLAYED")) { score[tileState] = (score[tileState] || 0) + 1 } }) scoreboard.innerHTML = JSON.stringify(score).replace("{", "").replace("}", "").replace(/"/g, "").replace(",", '
') this.model.tiles.forEach(({color, number, x, y, tileState}) => { let id = color + number if (!(tileState === "UNPICKED" || tileState === "PLAYED" || tileState === this.name)) { let elem = document.getElementById(id) if (elem) elem.remove() return } let elem = document.getElementById(id) if (!elem) { elem = document.createElement('div') elem.innerHTML = `
${number}
` elem.id = id elem.classList.add('tile') elem.ontouchend = e => setTimeout(() => { elem.moving = false elem.style["z-index"] = 1 }, 1000) elem.ontouchmove = e => { elem.moving = true let b = elem.getBoundingClientRect() let clientX = e.touches[0].clientX - (b.width /2) let clientY = e.touches[0].clientY - (b.height/2) elem.style.left = clientX + "px" elem.style.top = clientY + "px" elem.style["z-index"] = 999 let currentTileState = this.getTile({color, number}).tileState let newTileState if (inHand(e.touches[0].clientY + (b.height/2))) { newTileState = this.name } else if (currentTileState === this.name) { newTileState = "PLAYED" } this.publish("default", "updateTile", { color, number, x: clientX / window.innerWidth, y: clientY / window.innerHeight, tileState: newTileState }) } container.append(elem) } else if (!elem.moving) { elem.style.left = x * window.innerWidth + "px" elem.style.top = y * window.innerHeight + "px" } elem.firstChild.style.display = tileState === "UNPICKED" ? 'none' : 'block' }) } } Croquet.Session.join("Rummikub", Rummikub, MyView).then(r => window.r = r);