Skip to content

Instantly share code, notes, and snippets.

@stevekrouse
Created May 25, 2020 11:59
Show Gist options
  • Save stevekrouse/bfa382d84b33550326e7178f22913f01 to your computer and use it in GitHub Desktop.
Save stevekrouse/bfa382d84b33550326e7178f22913f01 to your computer and use it in GitHub Desktop.
Rummikub
<div id="container">
<div id="board">
</div>
<div id="hand">
</div>
<div id="scoreboard">
</div>
</div>

Rummikub

Croquet is a synchronization system for multiuser digital experiences.

Creating a Croquet application does not require the programmer to write separate client and server code. Applications are developed as though they are local, single-user experiences, and Croquet takes care of the rest.

A Pen by Steve Krouse on CodePen.

License.

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(",", '<br>')
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 = `<div style="color:${color}; font-size: 1.5em">${number}</div>`
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);
<script src="https://croquet.studio/sdk/croquet-latest.min.js"></script>
<script src="https://codepen.io/croquet/pen/vYYyNed"></script>
html, body, #container {
width: 100%;
height: 100%
}
* { margin: 0; padding: 0}
#widgets {
display: none;
}
#container {
background-color: beige;
}
#hand {
position: absolute;
left: 10px;
bottom: 10px;
width: calc(100% - 20px);
height: 30%;
background-color: brown;
border-radius: 10px;
}
.tile {
width: 30px;
height: 40px;
background-color: white;
position: absolute;
top: 10px;
left: 10px;
border: 1px solid black;
border-radius: 5px;
display: flex;
align-items: center;
justify-content: center;
user-select: none;
}
#scoreboard {
position: absolute;
right: 20px;
top: 10px;
}
<link href="https://codepen.io/croquet/pen/vYYyNed" rel="stylesheet" />
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment