Public
Edited
Jul 10, 2022
Insert cell
Insert cell
Insert cell
Insert cell
viewof text = Inputs.text({
label: "Room Name",
value: Math.floor(Math.random() * 10000000)
})
Insert cell
Insert cell
view = html`
<div class="o-view">
<div class="o-wrapper">
<canvas width="160" height="128" id="o-cnv"></canvas>
</div>
</div>`
Insert cell
style = html`
<style>
.o-wrapper {
margin: 0;
}
.o-view {
border: 2px solid black;
width: 640px;
height: 512px;
}
</style>
`
Insert cell
Insert cell
k = kaboom({
global: false, // kaboom only via singleton
width: 160, // 640 / 4 for scale
height: 128, // 480 / 4 for scale + fudge
canvas:document.querySelector("#o-cnv"), // can't use DOM.context2D for some reason
scale: 4, // tiles are small let's zoom
background: [256,0,256] // temporary background color
})
Insert cell
main = {
// where the magic happens
await loadSprites();
k.scene('main', scene_main);


k.go('main');

// Croquet stuff
MyModel.register("MyModel");
Croquet.Session.join({
apiKey: "1EfNtklMqmAIlv1FjA3bKdBzB4POms450Acc2g7FK",
appId: "com.observablehq.triptych.oubliette",
name: "unnamed",
password: "secret",
model: MyModel,
view: MyView
}
)
}
Insert cell
Insert cell
scene_main = async () => {
console.log("main scene!")
//await k.loadSprite("wall", await basicTiles.file("tile000.gif").url())
//await loadSprites();
console.log("wall", k.sprite("wall"))
// k.add([
// k.sprite("wall"),
// k.scale(k.width()/720, k.height()/720),
// ]);
const map = k.addLevel(levels[0], {
width: 16,
height: 16,
pos: k.vec2(0,0),
"#": () => [
k.sprite("wallside"),
k.solid(),
k.area()
],
"w": () => [
k.sprite("walltop"),
k.solid(),
k.area()
],
"t": () => [
k.sprite("walltopcorner"),
k.solid(),
k.area()
],
"b": () => [
k.sprite("wallbottomcorner"),
k.solid(),
k.area()
],
".": () => [
k.sprite("floor"),
k.area()
]
}); // end map

const player = k.add([
k.pos(map.getPos(2, 2)),
k.sprite("player"),
k.solid(),
k.origin("center"),
k.area({ width: 16, height: 16, offset: k.vec2(0, 8) }),
"player"
]); // end player

const SPEED = 220;
const dirs = {
"left": k.LEFT,
"right": k.RIGHT,
"up": k.UP,
"down": k.DOWN,
};

for (const dir in dirs) {
// onKeyPress(dir, () => {
// dialog.dismiss()
// })
k.onKeyDown(dir, () => {
player.move(dirs[dir].scale(SPEED))
console.log("player",player);
console.log("player pos", player.pos);
console.log("MyView:",MyView)
const customEvent = new CustomEvent('playermove', {
detail: {
x: player.pos.x,
y: player.pos.y
}
});
eventPipe.dispatchEvent(customEvent)
})
}
}
Insert cell
Insert cell
levels = [
[
`twwwwwwwwt`,
`#........#`,
`#........#`,
`#........#`,
`#........#`,
`#........#`,
`#........#`,
`bwwwwwwwwb`
]
]
Insert cell
Insert cell
loadSprites = async () => {
console.log(basicTiles);
// load game sprites
await k.loadSprite("walltop", await stillMoreTiles.file("tile000.gif").url())
await k.loadSprite("wallside", await stillMoreTiles.file("tile001.gif").url())
await k.loadSprite("walltopcorner", await stillMoreTiles.file("tile003.gif").url())
await k.loadSprite("wallbottomcorner", await stillMoreTiles.file("tile002.gif").url())
await k.loadSprite("floor", await stillMoreTiles.file("tile008.gif").url())

await k.loadSprite("player", await basicTiles.file("tile003.gif").url())
await k.loadSprite('entity', await basicTiles.file("tile001.gif").url())
console.log(k.sprite("wall"))
}
Insert cell
loadOther = async () => {
console.log("loadOther called");
mutable other = k.add(
[
k.pos(k.vec2(16*6,16*6)),
k.sprite("entity"),
k.solid(),
k.origin("center"),
k.area({ width: 16, height: 16, offset: k.vec2(0, 8) }),
"other"
]
)
}
Insert cell
loadEntity = async () => {
console.log("loadEntity called")
mutable entity = k.add(
[
k.pos(k.vec2(16*3,16*3)),
k.sprite("entity"),
k.solid(),
k.origin("center"),
k.area({ width: 16, height: 16, offset: k.vec2(0, 8) }),
"entity"
]
)
}
Insert cell
Insert cell
basicTiles = FileAttachment("ezgif-4-26b3cba000-png-16x16-sprite-gif.zip").zip()
Insert cell
moreTiles = FileAttachment("ezgif-4-bbb5424a18-png-16x16-sprite-gif.zip").zip()
Insert cell
stillMoreTiles = FileAttachment("ezgif-4-96df7444d2-png-16x16-sprite-gif.zip").zip()
Insert cell
class MyModel extends Croquet.Model {
init() {
this.views = new Map();
this.participants = 0;
this.history = [];
this.entity = {
name: "entity",
x: 4*16,
y: 4*16
}
// this.subscribe("entity", "move", this.moveEntity)
this.subscribe("entity", "reset", this.resetEntity);
this.subscribe(this.sessionId, "view-join", this.viewJoin);
this.subscribe(this.sessionId, "view-exit", this.viewExit);
this.subscribe("myChar", "updatePos", this.updatePos);

this.future(1000).tick()
};

updatePos(obj){
this.views.set(obj.id, {
nickname: this.views.get(obj.id).nickname,
x: obj.x,
y: obj.y
});
this.publish("viewInfo", "refresh");
}
resetEntity() {
this.entity.x = 4 * 16;
this.entity.y = 4 * 16;
this.publish("entity", "changed");
}
tick() {
this.entity.x += Math.floor(Math.random() * 10) - 5
if(this.entity.x < 0) {
this.entity.x = 0
}
this.entity.y += Math.floor(Math.random() * 10) - 5
if(this.entity.y < 0) {
this.entity.y = 0
}
this.publish("entity", "changed");
this.future(1000).tick()
}

viewJoin(viewId) {
const existing = this.views.get(viewId);
if (!existing) {
const nickname = this.randomName();
this.views.set(viewId, {
nickname,
x: 16 * 4,
y: 16 * 4
}
);
}
this.participants++;
this.publish("viewInfo", "refresh");
}

viewExit(viewId) {
this.participants--;
this.views.delete(viewId);
this.publish("viewInfo", "refresh");
}

randomName() {
const names =["Acorn", "Allspice", "Almond", "Ancho", "Anise", "Aoli", "Apple", "Apricot", "Arrowroot", "Asparagus", "Avocado", "Baklava", "Balsamic", "Banana", "Barbecue", "Bacon", "Basil", "Bay Leaf", "Bergamot", "Blackberry", "Blueberry", "Broccoli", "Buttermilk", "Cabbage", "Camphor", "Canaloupe", "Cappuccino", "Caramel", "Caraway", "Cardamom", "Catnip", "Cauliflower", "Cayenne", "Celery", "Cherry", "Chervil", "Chives", "Chipotle", "Chocolate", "Coconut", "Cookie Dough", "Chamomile", "Chicory", "Chutney", "Cilantro", "Cinnamon", "Clove", "Coriander", "Cranberry", "Croissant", "Cucumber", "Cupcake", "Cumin", "Curry", "Dandelion", "Dill", "Durian", "Earl Grey", "Eclair", "Eggplant", "Espresso", "Felafel", "Fennel", "Fig", "Garlic", "Gelato", "Gumbo", "Halvah", "Honeydew", "Hummus", "Hyssop", "Ghost Pepper", "Ginger", "Ginseng", "Grapefruit", "Habanero", "Harissa", "Hazelnut", "Horseradish", "Jalepeno", "Juniper", "Ketchup", "Key Lime", "Kiwi", "Kohlrabi", "Kumquat", "Latte", "Lavender", "Lemon Grass", "Lemon Zest", "Licorice", "Macaron", "Mango", "Maple Syrup", "Marjoram", "Marshmallow", "Matcha", "Mayonnaise", "Mint", "Mulberry", "Mustard", "Natto", "Nectarine", "Nutmeg", "Oatmeal", "Olive Oil", "Orange Peel", "Oregano", "Papaya", "Paprika", "Parsley", "Parsnip", "Peach", "Peanut Butter", "Pecan", "Pennyroyal", "Peppercorn", "Persimmon", "Pineapple", "Pistachio", "Plum", "Pomegranate", "Poppy Seed", "Pumpkin", "Quince", "Raspberry", "Ratatouille", "Rosemary", "Rosewater", "Saffron", "Sage", "Sassafras", "Sea Salt", "Sesame Seed", "Shiitake", "Sorrel", "Soy Sauce", "Spearmint", "Strawberry", "Strudel", "Sunflower Seed", "Sriracha", "Tabasco", "Tahini", "Tamarind", "Tandoori", "Tangerine", "Tarragon", "Thyme", "Tofu", "Truffle", "Tumeric", "Valerian", "Vanilla", "Vinegar", "Wasabi", "Walnut", "Watercress", "Watermelon", "Wheatgrass", "Yarrow", "Yuzu", "Zucchini"];
return names[Math.floor(Math.random() * names.length)];
}
}
Insert cell
class MyView extends Croquet.View {
constructor(model) {
super(model);
this.model = model;
this.subscribe("entity", "changed", this.entityChanged);
document.getElementById('btn-entity-reset').addEventListener("click", (event)=> this.entityReset())
resetHtml.addEventListener("click", (event)=> {
console.log("myview entity reset")
this.entityReset();
})

eventPipe.addEventListener('playermove', (event) => {
console.log("eventpipe event detail x", event.detail.x);
console.log("eventpipe event detail y", event.detail.y);
console.log(" eventpipe model", this.model)
// this.model.views.set(this.viewId, {
// nickname: this.model.views.get(this.viewId).nickname,
// x: event.detail.x,
// y: event.detail.y
// });
// this.publish("viewInfo", "refresh")
this.publish("myChar", "updatePos", {
id: this.viewId,
x: event.detail.x,
y: event.detail.y
})
});

// this.subscribe("history", "refresh", this.refreshHistory);
this.subscribe("viewInfo", "refresh", this.refreshViewInfo);
// kick things off
this.refreshViewInfo();

this.entityChanged();
}
entityReset() {
this.publish('entity', 'reset')
}
entityChanged() {
let localX = this.model.entity.x;
let localY = this.model.entity.y
document.getElementById('entity-x').textContent = localX
document.getElementById('entity-y').textContent = localY
if( mutable entity) {
console.log("entity x, y", {localX, localY})
mutable entity.moveTo(localX, localY)
}
}

refreshViewInfo() {
let me = this.model.views.get(this.viewId)
console.log( "me nickname:", me.nickname);
console.log( "me x", me.x);
console.log( "me y", me.y);
console.log( "participants:", this.model.participants)
if(this.model.participants > 1){
console.log("model.participants:", this.model.participants);
console.log('mutable other', mutable other);
if(!mutable other){
loadOther();
}
for (const el of this.model.views) {
if(el[0] == this.viewId){
console.log("viewId match, skip")
} else {
mutable other.moveTo(el[1].x, el[1].y)
}
}
}
// if(this.model.participants > 1){
// for( const el of this.model.views) {
// console.log("el", el);
// console.log("this.viewId", this.viewId)
// if(el[0] != this.viewId){
// console.log("in other el")
// if(!other) {
// console.log('no other')
// // loadOther();
// }
// if(other) {
// other.moveTo(el[1].x,el[1].y)
// }
// }
// }
// }
}

}
Insert cell
Insert cell
kaboom = (await import('https://unpkg.com/kaboom@2000.2.9/dist/kaboom.mjs?module')).default
Insert cell
Insert cell
Insert cell
Insert cell
Croquet = require("@croquet/croquet@1.0").catch(e => window.Croquet)
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
{
console.log(await basicTiles.file("tile000.gif").url())

//loadSprites()
}
Insert cell
debugEntityView = html`<div id="debug-entity-view">Entity: x:<span id="entity-x">?</span> y: <span id="entity-y">?</span></div>`
Insert cell
viewof button = Inputs.button("Load Entity", {
reduce: () => {
console.log("click load entity")
loadEntity();
}
})
Insert cell
resetEntity = html`<button id='btn-entity-reset'>Reset Entity</button>`
Insert cell
{
resetEntity.addEventListener('click', ()=> {
console.log('resetEntity called')
})
}
Insert cell
resetHtml = htl.html`<div><button>reset Entity 2</button></div>`
Insert cell
mutable entity = null;
Insert cell
mutable other = null;
Insert cell
eventPipe = html`<div id='eventPipe'>Event Pipe</div>`
Insert cell
{

}
Insert cell

One platform to build and deploy the best data apps

Experiment and prototype by building visualizations in live JavaScript notebooks. Collaborate with your team and decide which concepts to build out.
Use Observable Framework to build data apps locally. Use data loaders to build in any language or library, including Python, SQL, and R.
Seamlessly deploy to Observable. Test before you ship, use automatic deploy-on-commit, and ensure your projects are always up-to-date.
Learn more