diff --git a/.gitignore b/.gitignore index 2c405a2..cd8d535 100644 --- a/.gitignore +++ b/.gitignore @@ -14,4 +14,5 @@ canisters/ .dfx/ # frontend code +package-lock.json node_modules/ diff --git a/README.md b/README.md index c06de8a..06088bd 100644 --- a/README.md +++ b/README.md @@ -11,3 +11,8 @@ Click the above icon to play online! - `dfx build` - `dfx canister install --all` - visit `127.0.0.1:8000/?canisterId={maze_assets_canister_id}` + +# Start peerjs server locally + +- install: `npm install peer -g` +- `peerjs --port 9000 --key peerjs --path /myapp` diff --git a/src/main.mo b/src/main.mo index 68529d3..b3d3502 100644 --- a/src/main.mo +++ b/src/main.mo @@ -173,20 +173,23 @@ actor { let id = msg.caller; maze.move(id, dir); }; + // Not needed. For debugging only public query func getState() : async [OutputState] { maze.outputState() }; // Output: // - Array of non-empty cells - // - Processed sequence number + // - Processed sequence number public query(msg) func getMap() : async ([OutputGrid], Nat) { (maze.outputMap(), maze.getSeq(msg.caller)) }; - public query(msg) func fakeMove(dirs : [Msg]) : async ([OutputGrid], Nat) { - let id = msg.caller; + // query move takes pending moves from all players + public query(msg) func fakeMove(dirs : [(Principal, [Msg])]) : async ([OutputGrid], Nat) { let processedSeq = maze.getSeq(msg.caller); - for (dir in dirs.vals()) { - maze.move(id, dir); + for ((id, msgs) in dirs.vals()) { + for (m in msgs.vals()) { + maze.move(id, m); + }; }; (maze.outputMap(), processedSeq) }; diff --git a/src/maze_assets/index.js b/src/maze_assets/index.js index f4fa4b9..1df0e72 100644 --- a/src/maze_assets/index.js +++ b/src/maze_assets/index.js @@ -1,5 +1,6 @@ import canister from 'ic:canisters/maze'; import './maze.css'; +import { CanisterId } from '@dfinity/agent'; // util for creating maze @@ -8,7 +9,6 @@ const N = 10; const symbols = [ "", "wall", "hero" ]; async function generateMaze(dom) { - console.log("generateMaze"); let f = await canister.getMap(); // First create the empty map for (let i = 0; i < N; i++) { @@ -56,26 +56,59 @@ async function mazeKeyPressHandler(e) { const msg = {}; msg.seq = myseq++; msg.dir = dir; + pendingMoves.push(msg); // Call move without waiting for reply canister.move(msg); - // Query move with pendingMoves - pendingMoves.push(msg); - const tmp = await canister.fakeMove(pendingMoves); + // Update playMessages for myself + playerMessages[myid] = pendingMoves; + // Send p2p msgs + const others = state.getOtherPlayers(); + others.forEach(id => { + const conn = peer.connect(id); + conn.on('open', () => { + console.log("send", pendingMoves); + conn.send([myid, pendingMoves]); + //conn.send("hi"); + }); + }); + + // Query move with playerMessages + // construct candid format + const messages = []; + for (const id in playerMessages) { + const m = { + _0_: CanisterId.fromHex(id), + _1_: playerMessages[id] + }; + messages.push(m); + } + const tmp = await canister.fakeMove(messages); tmpState.update(tmp); // Remove processed moves pendingMoves = pendingMoves.filter(m => m.seq >= processedSeq); + await render(); e.preventDefault(); } async function render() { + // Draw confirmed state const res = await canister.getMap(); state.update(res); + const pending = myseq - processedSeq; score.innerText = processedSeq.toString(); if (pending > 0) score.innerText += " pending: " + pending.toString(); + // receive p2p msgs + peer.on('connection', conn => { + conn.on('data', data => { + console.log("recv", data); + const [id, pending] = data; + playerMessages[id] = pending; + }); + }); } class Pos { @@ -104,24 +137,37 @@ class Map { } else return "" } + getOtherPlayers() { + const list = []; + for (const pos in this._grids) { + const person = this._grids[pos].person; + if (typeof person !== 'undefined') { + const id = idToPeerId(person); + if (id !== myid) { + list.push(id); + } + } + } + return list; + } // update map and draw the diff update(g) { processedSeq = g[1]; const new_grids = []; g[0].forEach(grid => { const pos = Pos.fromPos(grid._0_); - new_grids[pos] = Map.getGridType(grid._1_); + new_grids[pos] = grid._1_; }); for (const pos in this._grids) { - const type = this._grids[pos]; + const type = Map.getGridType(this._grids[pos]); grids[pos].classList.remove(type); if (!this._isFinal) { grids[pos].classList.remove("temp"); } } for (const pos in new_grids) { - const type = new_grids[pos]; + const type = Map.getGridType(new_grids[pos]); grids[pos].classList.add(type); if (!this._isFinal) { grids[pos].classList.add("temp"); @@ -143,11 +189,22 @@ let tmpState = new Map(false); let myid; let myseq; let processedSeq; +let peer; +let playerMessages = []; const score = document.createElement('div'); score.id = "maze_score"; -async function init() { +function idToPeerId(principal) { + return principal.toText().slice(3, -2); +} + +function init() { + const link = document.createElement('script'); + link.setAttribute('src', 'https://cdn.jsdelivr.net/npm/peerjs@1.2.0/dist/peerjs.min.js'); + link.setAttribute('type', 'text/javascript'); + document.getElementsByTagName("head")[0].appendChild(link); + const container = document.createElement('div'); container.id = "maze_container"; const maze = document.createElement('div'); @@ -160,7 +217,7 @@ async function init() { let div = document.createElement('div'); div.id = "maze_message"; - document.body.appendChild(div); + document.body.appendChild(div); document.addEventListener("keydown", mazeKeyPressHandler, false); @@ -171,9 +228,18 @@ async function init() { join.addEventListener('click', () => { (async () => { const res = await canister.join(); - myid = res[0]; + myid = idToPeerId(res[0]); myseq = res[1].toNumber(); - setInterval(render, 200); + // create p2p object + peer = new Peer(myid, { debug: 2 }); + /*peer = new Peer(peerid, { + host: 'localhost', + port: 9000, + path: '/myapp', + debug: 2 + });*/ + // set timer + //setInterval(render, 200); })(); }); } diff --git a/src/maze_assets/maze.css b/src/maze_assets/maze.css index 89333b6..5e41bd2 100644 --- a/src/maze_assets/maze.css +++ b/src/maze_assets/maze.css @@ -45,7 +45,6 @@ content: "\1F381"; } #maze div.temp::after { - background-color: #45aa45; opacity: 0.6; } #maze div.hero::after {