Independent Brand
自研品牌
Culture Activity
文化活动

来了解真实的奇舞团—2019小年会
奇舞团小年会(又名奇舞团年度零食会、奇舞团最大型抽奖活动)1月10日在360大厦成功举行。 Three two one go~ 第一趴是各组 leader 述职,乍一听,嗯?这咋跟抽奖没啥关系呢,零食看起来都不香了呢~ 说啥呢,好好好,钱能解决的问题都是好问题。这个环节是可以赚钱的呀,孩子。为什么呢? 有红包呢,这个环节时间是用钱来买的哦,每个人只有5分钟时间述职,他们想跟大家多聊聊,大家有的是时间,现场陪聊可还行?由我们一位可爱的同学负责计时,为了确保大家现场陪聊的工资能按时足量发放,我们成银大大担任我们的公证人,负责帮大家确定红包额度,真是辛苦您了。 “热切希望对React Hooks感兴趣、有了解、研究过、想尝试的小伙伴加入我们组......” 诶诶诶,磊哥你干啥呢,咋还现场挖人呢,磊哥开了头之后,现场招聘广告满天飞,群主都管不住了的那种,360奇舞团内部人才招聘市场它怎么就开始了呢? 咦,这是哪个leader的PPT卡住了呢,是不是没用在线制作PPT的声享(http://ppt.baomitu.com)呀,那要不你先改一下PPT,这个时间我们干点什么呢,不如唱个歌吧,说唱就唱,音响师,灯光师,歌手请就位~ 想不到吧,述职居然可以有现场听,开心心~ 谢谢你! 感谢在这一年里为我们奇舞团基础建设做出突出贡献的兄弟姐妹(的代表)~ 他们是天天跟大家见面的奇舞周刊的白月光(小编)——付某(负责奇舞周刊周五周刊三年半)、陈某某(负责周三日刊一年半)//此处有优秀俩字。 感谢为我们招新同学的最勤劳面试官——怡红公子。 泛前端分享前四名的同学—分别是我们的新同学 Hax、刘某某、郝某某和韩某某(周刊小编)这四位同学(某几位大佬慷慨放弃奖励之后的排名,让我们一起感谢各位大佬[鞠躬~]),恭喜四位同学。新同学Hax多次在泛前端的分享,奇舞团的同学都觉得受益匪浅。 还有为我们辛苦付出很多的我们部门的 hr 小姐姐,感谢您~ 这张照片怎么从左到右、从右到左看都觉得我们奇舞团不太年轻呢!朋友,那是昨天临时组成的颁奖嘉宾奇舞团老年组F4呀小老弟。 咳咳,让我中个奖 它来了它来了,它带着一大堆奖品来了~ 这一大堆奖品我们要怎么发呢,由于前几天在大年会上嘲笑刚开始中了三等奖的我团同学,为了不让这种影响同事间塑料友谊的事情再度发生,我决定在抽奖的时候只让大家拿到奖项,最后统一公布奖项对应奖品,那我准备了哪些奖项呢? 哇,看到这个名字,终于不用在抽奖的时候嘲笑同事中了末等奖了,同事间的塑料友谊可算是保住了,惊吓惊吓! 抽奖程序现场写 奇舞团年会的最后保留节目!!! 大家期待的月影大大现场亲自操刀的抽奖程序环节(历年抽奖代码GitHub地址:https://github.com/75team/raffle)。 月影大大说,今年不用 CPU 抽,用 GPU 抽。对于传统的前端开发者来说,对于可视化,深层的WebGL没有很深入的理解。想要了解更多,那就来奇舞团吧!此处强烈安利开源的跨平台的高性能图形系统SpriteJS ,最近于2019年发布新版本的SpriteJS 3.0(https://spritejs.org/#/)针对可视化大屏渲染优化性能提升10倍以上哦~~~毫无广告痕迹的广告。 一波彩虹屁~ 哇,正在写抽奖程序的月影大大看起来格外帅 //我不管,我吹的彩虹屁必须让领导看到!!! 要是我也可以中奖就更帅呢(dbq,我就是这么现实 。。。。。。。。。。。。。五十多个奖抽完了,总共85个人(这是铺垫 后边的流程我也不想管了,你们想干嘛干嘛吧,我走了,奇舞团容不下我了。 快乐是他们的,我什么都没有 妈妈,就是这些人把奖品都中走了 不让我中奖的代码都不是......... 祝你们开心,不要劝我! 等一下,AirPods pro没人要了吗 咚咚咚...你的小金库可爱的上线~ 成银:我和月影再一人捐个 AirPods pro 吧 内心OS:好,那我不走了。。。没中奖的人不多了,我的机会不就来了吗(哎呀,我准备好了,已经没有什么能阻挡我中奖了,谢谢大家 成银接着说:但是我们要重新开始抽,就是85个人(承上启下,强不强)都有中奖的机会。 开始抽奖~ 咦,这个中奖的名字怎么有点眼熟,这不是我吗,妈耶,真的好善良哦~我的劝说是有用的哎,我拜的财神爷终于想起我了吗??? OMG!!!!! 心诚自然灵♪(^∀^●)♪ 月影写的抽奖代码真棒,写抽奖代码的月影超帅der,捐奖的成银似乎更白了一点,头发多了一点呢~ Happy endding~ 祝大家新年快乐~
Fri Sep 11 2020 07:50:02 GMT+0000 (UTC)

嘿!这真的是一个正经的抽奖程序!
奇舞团有一个传统项目,每年年会由我在现场写一个抽奖程序,所有人一起review代码,以确保抽奖算法正确且公平,然后愉快滴开始抽奖。 现场写的抽奖程序不仅要公平无bug,而且还要有一定的趣味性,且不能和往年的重复。 2017年年会我写了一个随机抽纸牌中奖1的程序,而今年年会,我灵机一动????,决定写一个更有(不)趣(正)味(经)的抽奖程序。 长话短说,我们就着代码一步步往下看。 首先是常规随机洗牌三连: function random(m, n) { return m + Math.floor(Math.random() * n); } function randomItem(arr, from = 0, to = arr.length) { const index = random(from, to); return { index, value: arr[index], }; } function shuffle(arr) { for(let i = arr.length; i > 0; i--) { const {index} = randomItem(arr, 0, i); [arr[index], arr[i - 1]] = [arr[i - 1], arr[index]]; } return arr; }上面的代码没有什么特别的,只是一个朴实的洗牌算法,额外封装两个随机函数,因为后面的代码中还要使用。对比一下,2017年版的代码更加“妖艳”: function* generatePoker() { const points = ['A', 2, 3, 4, 5, 6, 7, 8, 9, 10, 'J', 'Q', 'K']; yield* points.map(p => ['♠️', p]); yield* points.map(p => ['♣️', p]); yield* points.map(p => ['♥️', p]); yield* points.map(p => ['♦️', p]); } const cards = generatePoker(); class PickedCards { constructor(key, storage = localStorage) { this.key = key; this.storage = storage; this.cards = JSON.parse(storage.getItem(key)) || []; this.cardSet = new Set(this.cards.map(card => card.join(''))); } add(card) { this.cards.push(card); this.cardSet.add(card.join('')); this.storage.setItem(this.key, JSON.stringify(this.cards)); } has(card) { return this.cardSet.has(card.join('')); } clear() { this.storage.clear(); } } const pickedCards = new PickedCards('pickedCards'); function* shuffle(cards, pickedCards) { cards = [...cards]; cards = cards.filter(card => !pickedCards.has(card)); let len = cards.length; while(len) { const i = Math.floor(Math.random() * len); pickedCards.add(cards[i]); yield cards[i]; [cards[i], cards[len - 1]] = [cards[len - 1], cards[i]]; len--; } }2017版的随机扑克牌代码 有了上面朴实的随机代码,理论上我们就可以愉快滴抽奖了: function random(m, n) { return m + Math.floor(Math.random() * n); } function randomItem(arr, from = 0, to = arr.length) { const index = random(from, to); return { index, value: arr[index], }; } function shuffle(arr) { for(let i = arr.length; i > 0; i--) { const {index} = randomItem(arr, 0, i); [arr[index], arr[i - 1]] = [arr[i - 1], arr[index]]; } return arr; } let members = ['胖虎', '强夫', '静香', '大雄', '哆啦A梦', '吕布', '张飞', '关羽', '刘备', '曹操', '孙权', '周瑜', '黄盖', '赵云', '吕蒙', '孙悟空', '猪八戒', '唐僧', '沙悟净', '光头强', '熊大', '熊二', '喜洋洋', '美羊羊', '红太狼', '灰太狼', ]; console.log(shuffle(members).slice(-3)); // 抽取3名获奖者 当然,我们不会只是这么无聊滴抽取,还是要玩点花样,不然怎么好意思说自己是前端呢?HTML必须有: <!DOCTYPE html> <html> <head> <meta charset="utf-8"> <meta name="viewport" content="width=device-width"> <title>一起抽奖吧</title> <link rel="stylesheet" href="style.css"> </head> <body> <div id="control"><button id="start">开始</button><button id="clear">清空</button></div> <div id="track"> <div><span class="horse"></span><span class="player">1</span></div> <div><span class="horse"></span><span class="player">2</span></div> <div><span class="horse"></span><span class="player">3</span></div> <div><span class="horse"></span><span class="player">4</span></div> <div><span class="horse"></span><span class="player">5</span></div> <div><span class="horse"></span><span class="player">6</span></div> </div> <script src="app.js"></script> </body> </html>CSS简单写一个: html, body { padding: 0; margin: 0; width: 100%; height: 100%; } #control { text-align: center; line-height: 120px; } #control button { font-size: 2rem; margin: 0 10px; } #track { max-width: 1250px; max-height: 500px; border-top: solid 1px #aaa; } #track div { position: relative; height: 100px; line-height: 100px; font-size: 1.5rem; padding: 0 10px; color: #aaa; } #track .player { float: right; } #track .horse { display: inline-block; font-size: 5rem; transform: scale(-1, 1); position: absolute; left: 850px; top: 0; z-index: 99999; } #track .horse span { display: inline-block; transform: scale(-1, 1); } #track div:nth-child(2n) { background: #666; } #track div::after { content: ' '; position: absolute; left: 930px; width: 20px; height: 100%; background: #333; }这个玩法呢,就是个跑马小游戏: 接下来,我们开始完善JS代码。为了记录抽奖结果和连续抽奖(每个人只能中一次奖),避免不小心刷新了页面,导致结果丢失,我们用localStorage存一下: const prizeStorageKey = 'prize10'; function addResults(players) { const result = getResults(); result.push(...players); localStorage.setItem(prizeStorageKey, result.join()); }我们如果不小心刷新了页面,重新开始抽奖之前,我们要把已经中过奖的小伙伴从列表里剔除: function getResults() { const result = localStorage.getItem(prizeStorageKey); return result ? result.split(',') : []; } function filterWinner(members) { const winners = new Set(getResults()); return members.filter(m => !winners.has(m)); } members = filterWinner(members);然后我们可以点【开始】按钮抽奖,点【清除】按钮清除localStorage记录。 const startBtn = document.getElementById('start'); const clearBtn = document.getElementById('clear'); startBtn.addEventListener('click', async () => { startBtn.disabled = 'disabled'; clearBtn.disabled = 'disabled'; // 重新洗牌 shuffle(members); // 取出最后6名同学,倒数3名中奖,剩下3名凑数 const candidates = members.slice(-6).reverse(); // 将中奖结果保存到localStorage中 addResults(candidates.slice(0, 3)); members.length -= 3; // 开始跑马程序 await race(candidates); startBtn.disabled = ''; clearBtn.disabled = ''; }); clearBtn.addEventListener('click', () => { // 清除所有中奖记录 localStorage.removeItem(prizeStorageKey); });接下来就是实现关键的跑马程序了。 其实我们的中奖结果在跑马程序开始前就已经出来了,跑马程序只是运行动画效果,和中奖结果无关。 最简单的一种方式就是根据排名依次从短到长,生成跑马总时间,然后将6个人随机到不同的赛道开始跑马: function race(candidates) { const durations = []; for(let i = 0, duration = 0.9; i < candidates.length; i++) { durations.push(duration); // 每一名次随机增加 0.02 ~ 0.05 的时间 duration += random(2, 5) * 0.01; } const players = shuffle([...candidates.entries()]); ... }但是这样有个问题,就是跑马的时候,名次落后的时间长速度慢,名次靠前的速度快始终跑在前面,胜负毫无悬念,也就失去了赛马的意义。所以要做随机,以保留悬念。 产生随机有很多种方法,这里用一种最简单的方法,就是把一次比赛分成若干个小阶段,每个小阶段分配一个基准时间,但是允许每个选手在该阶段时间有一定的正负扰动。比如: A选手跑完全程时间8秒钟,B选手跑完全程时间为10秒钟,我们第一阶段先取路程的1/4,A跑完1/4程的基准时间是2秒,B是2.5秒,假设扰动参数为正负0.5,那么A跑完1/4程的时间最多是2+0.5=2.5秒,而B跑完1/4程的时间最少是2.5-0.5=2.0秒,这样就有可能在前1/4赛程里A选手反而落后B选手了。多分几个赛程,就可以有足够的悬念。 我们先定义划分赛程的函数: function partRace(durations, factor) { // 根据赛程总时间 duration 和 factor 来划分赛程 // 赛程所用基准时间为 duration * factor,扰动 -0.1 ~ +0.1 const subDuration = durations.map(d => d * factor * random(9, 11) / 10); subDuration.map((d, i) => { durations[i] -= d; return durations[i]; }); return subDuration; }这样我们把全程划分4段赛程: function race(candidates) { const durations = []; for(let i = 0, duration = 0.9; i < candidates.length; i++) { durations.push(duration); // 每一名次随机增加 0.02 ~ 0.05 的时间 duration += random(2, 5) * 0.01; } // 划分4段赛程 const round1 = partRace(durations, 0.25); const round2 = partRace(durations, 0.33); const round3 = partRace(durations, 0.5); const round4 = durations.map(d => d + 0.1); ... }这里面还有一个小技巧,我们划分赛程的时候,给最后一轮留下10%的时间,这是为了避免前面几轮赛程积累的随机扰动使得最后一程的时间太短。 这样我们就可以绘制赛马动画了: function partRace(durations, factor) { // 根据赛程总时间 duration 和 factor 来划分赛程 // 赛程所用基准时间为 duration * factor,扰动 -0.1 ~ +0.1 const subDuration = durations.map(d => d * factor * random(9, 11) / 10); subDuration.map((d, i) => { durations[i] -= d; return durations[i]; }); return subDuration; } function race(candidates) { const durations = []; for(let i = 0, duration = 0.9; i < candidates.length; i++) { durations.push(duration); // 每一名次随机增加 0.02 ~ 0.05 的时间 duration += random(2, 5) * 0.01; } const players = shuffle([...candidates.entries()]); trackEl.innerHTML = players.map((p, i) => { return `<div> <span class="horse">${randomItem(['????', '????', '????', '????']).value}</span> <span class="player">${p[1]} ${i + 1}</span> </div>`; }).join(''); // 划分4段赛程 const round1 = partRace(durations, 0.25); const round2 = partRace(durations, 0.33); const round3 = partRace(durations, 0.5); const round4 = durations.map(d => d + 0.1); const results = ['????', '????', '????', '????', '????', '????']; const T = 8000; const horses = document.querySelectorAll('.horse'); const promises = []; for(let i = 0; i < horses.length; i++) { const horse = horses[i]; const idx = players[i][0]; promises.push(raceHorse(horse, round1[idx] * T) .then(() => { return raceHorse(horse, round2[idx] * T, 30 + trackLen / 4); }) .then(() => { return raceHorse(horse, round3[idx] * T, 30 + 2 * trackLen / 4); }) .then(() => { return raceHorse(horse, round4[idx] * T, 30 + 3 * trackLen / 4); }) .then(() => { horse.innerHTML = `<span>${results[idx]}</span>${horse.innerHTML}`; return raceHorse(horse, 0.1 * T, 30 + trackLen, 100); })); } return Promise.all(promises); }具体的raceHorse就是一个简单的DOM匀速动画绘制过程: function raceHorse(horseEl, duration, from = 30, by = trackLen / 4) { return new Promise((resolve) => { const startTime = Date.now(); requestAnimationFrame(function f() { let p = (Date.now() - startTime) / duration; p = Math.min(p, 1.0); horseEl.style.left = `${from + p * by}px`; if(p < 1.0) requestAnimationFrame(f); else resolve(); }); }); }把完整的代码汇总一下: function random(m, n) { return m + Math.floor(Math.random() * n); } function randomItem(arr, from = 0, to = arr.length) { const index = random(from, to); return { index, value: arr[index], }; } function shuffle(arr) { for(let i = arr.length; i > 0; i--) { const {index} = randomItem(arr, 0, i); [arr[index], arr[i - 1]] = [arr[i - 1], arr[index]]; } return arr; } const prizeStorageKey = 'prize10'; function getResults() { const result = localStorage.getItem(prizeStorageKey); return result ? result.split(',') : []; } function addResults(players) { const result = getResults(); result.push(...players); localStorage.setItem(prizeStorageKey, result.join()); } function filterWinner(members) { const winners = new Set(getResults()); return members.filter(m => !winners.has(m)); } let members = ['胖虎', '强夫', '静香', '大雄', '哆啦A梦', '吕布', '张飞', '关羽', '刘备', '曹操', '孙权', '周瑜', '黄盖', '赵云', '吕蒙', '孙悟空', '猪八戒', '唐僧', '沙悟净', '光头强', '熊大', '熊二', '喜洋洋', '美羊羊', '红太狼', '灰太狼', ]; members = filterWinner(members); const startBtn = document.getElementById('start'); const clearBtn = document.getElementById('clear'); startBtn.addEventListener('click', async () => { startBtn.disabled = 'disabled'; clearBtn.disabled = 'disabled'; // 重新洗牌 shuffle(members); // 取出最后6名同学,倒数3名中奖,剩下3名凑数 const candidates = members.slice(-6).reverse(); // 将中奖结果保存到localStorage中 addResults(candidates.slice(0, 3)); members.length -= 3; // 开始跑马程序 await race(candidates); startBtn.disabled = ''; clearBtn.disabled = ''; }); clearBtn.addEventListener('click', () => { // 清除所有中奖记录 localStorage.removeItem(prizeStorageKey); }); const trackLen = 820; // 205 * 4 const trackEl = document.getElementById('track'); function partRace(durations, factor) { // 根据赛程总时间 duration 和 factor 来划分赛程 // 赛程所用基准时间为 duration * factor,扰动 -0.1 ~ +0.1 const subDuration = durations.map(d => d * factor * random(9, 11) / 10); subDuration.map((d, i) => { durations[i] -= d; return durations[i]; }); return subDuration; } function race(candidates) { const durations = []; for(let i = 0, duration = 0.9; i < candidates.length; i++) { durations.push(duration); // 每一名次随机增加 0.02 ~ 0.05 的时间 duration += random(2, 5) * 0.01; } const players = shuffle([...candidates.entries()]); trackEl.innerHTML = players.map((p, i) => { return `<div> <span class="horse">${randomItem(['????', '????', '????', '????']).value}</span> <span class="player">${p[1]} ${i + 1}</span> </div>`; }).join(''); // 划分4段赛程 const round1 = partRace(durations, 0.25); const round2 = partRace(durations, 0.33); const round3 = partRace(durations, 0.5); const round4 = durations.map(d => d + 0.1); const results = ['????', '????', '????', '????', '????', '????']; const T = 8000; const horses = document.querySelectorAll('.horse'); const promises = []; for(let i = 0; i < horses.length; i++) { const horse = horses[i]; const idx = players[i][0]; promises.push(raceHorse(horse, round1[idx] * T) .then(() => { return raceHorse(horse, round2[idx] * T, 30 + trackLen / 4); }) .then(() => { return raceHorse(horse, round3[idx] * T, 30 + 2 * trackLen / 4); }) .then(() => { return raceHorse(horse, round4[idx] * T, 30 + 3 * trackLen / 4); }) .then(() => { horse.innerHTML = `<span>${results[idx]}</span>${horse.innerHTML}`; return raceHorse(horse, 0.1 * T, 30 + trackLen, 100); })); } return Promise.all(promises); } function raceHorse(horseEl, duration, from = 30, by = trackLen / 4) { return new Promise((resolve) => { const startTime = Date.now(); requestAnimationFrame(function f() { let p = (Date.now() - startTime) / duration; p = Math.min(p, 1.0); horseEl.style.left = `${from + p * by}px`; if(p < 1.0) requestAnimationFrame(f); else resolve(); }); }); }最终的效果: 以上就是今年奇舞团年会的抽奖程序,大家有什么想法可以关注我们的GitHub仓库2与我们交流。 2019年年会的抽奖程序我也已经想好了,只会更加有趣,不过暂时不能透露????,2019年年会继续加油~ 文内链接: https://github.com/75team/raffle/tree/master/2017 https://github.com/75team/raffle
Fri Sep 11 2020 07:50:25 GMT+0000 (UTC)
Technology Activity
技术活动
About Us
关于我们
