컬러닷지: 두 판 사이의 차이
편집 요약 없음 |
잔글편집 요약 없음 태그: 되돌려진 기여 |
||
1번째 줄: | 1번째 줄: | ||
{{ | /* | ||
. | --[[사용자:BANIP|BANIP]] ([[사용자토론:BANIP|토론]]) 2023년 8월 12일 (토) 03:30 (KST) | ||
*/ | |||
const TIME_LIMIT = 60 | |||
const TIME_ADDITONAL = 20 | |||
const RATINGS = [ | |||
{icon:"battery_1_bar",message:"가능성만 있어요.",limit:100}, | |||
{icon:"battery_2_bar",message:"익히셨군요.",limit:200}, | |||
{icon:"battery_3_bar",message:"기대 이상이에요.",limit:300}, | |||
{icon:"battery_4_bar",message:"자랑해도 될 수준이에요.",limit:400}, | |||
{icon:"battery_5_bar",message:"익숙하시군요.",limit:500}, | |||
{icon:"battery_6_bar",message:"완벽에 가까워요.",limit:600}, | |||
{icon:"battery_full",message:"더이상 무엇을 바라겠어요?",limit:700}, | |||
] | |||
const MUSICS = [ | |||
{pagename:"파일:Popcandy goorogi.mp3", step:0}, | |||
{pagename:"파일:Cyrf pluto.mp3", step:20}, | |||
{pagename:"파일:Isao blaze.mp3", step:40}, | |||
] | |||
const sound = { | |||
intro: sys.initSound("dodge_intro.mp3"), | |||
result: sys.initSound("dodge_result.mp3"), | |||
move: sys.initSound("dodge_move.mp3"), | |||
success: sys.initSound("dodge_success.mp3"), | |||
count: sys.initSound("dodge_count.mp3"), | |||
start: sys.initSound("dodge_start.mp3"), | |||
} | |||
async function main() { | |||
await sys.init(); | |||
while(true){ | |||
bgm.stop(); | |||
await pages.lobby(); | |||
let {score} = await pages.game(); | |||
await pages.result({score}); | |||
} | } | ||
} | |||
String.prototype.parseHtml = function(){ | |||
return this.replace(/{/g, "<").replace(/}/g, ">"); | |||
} | |||
const util = { | |||
asleep: async (ms) => { | |||
return new Promise(resolve => setTimeout(resolve, ms)); | |||
}, | |||
} | |||
const pages = { | |||
$body: $("#mw-content-text"), | |||
lobby: () => { | |||
return new Promise(resolve => { | |||
pages.$body.html(` | |||
{div class="game lobby"} | |||
{div class="title"}{span class="skyblue"}컬러{/span} {span class="rose"}닷지{/span}{/div} | |||
{div class="btn start"}시작하기{/div} | |||
{/div} | |||
`.parseHtml()); | |||
//debugger | |||
let childSelectors = ".skyblue, .rose, .start"; | |||
pages.$body.find(childSelectors).css({display:"none"}); | |||
for( let [i, el] of Object.entries(pages.$body.find(childSelectors).toArray())){ | |||
if(!el) continue; | |||
setTimeout(() => { | |||
$(el).slideDown(500); | |||
}, i*500); | |||
} | |||
pages.$body.find(".btn").on("click", async () => { | |||
sound.move(); | |||
pages.$body.find(childSelectors).fadeOut(500); | |||
await util.asleep(500); | |||
resolve(); | |||
}); | |||
}); | |||
}, | |||
game: () => { | |||
let $body = pages.$body; | |||
$body.html(` | |||
{div class="game play"} | |||
{div class="bg"}{/div} | |||
{div class="timer-wrapper"}{/div} | |||
{div class="grid-wrapper"}{/div} | |||
{/div} | |||
`.parseHtml()); | |||
let gridGen = function*(){ | |||
let inits = [0,1,4,2] | |||
let repeat = [5,4,2,Infinity] | |||
let now = [0,0,0,0] | |||
while(true){ | |||
let getValue = (i) => now[i] + inits[i] | |||
for(let i = 0; i < now.length; i++){ | |||
if(now[i] >= repeat[i]){ | |||
now[i] = 0; now[i+1] += 1;inits[i] += 1; | |||
} | |||
} | |||
yield new Grid({shuffleCount:getValue(1), row:getValue(2), col:getValue(2), depth:getValue(3)}) | |||
now[0] += 1; | |||
} | |||
} | |||
let gridIter = gridGen(); | |||
let timer = new Timer(TIME_LIMIT) | |||
let grid = gridIter.next().value | |||
$body.find(".timer-wrapper").html(timer.getElement()); | |||
$body.find(".grid-wrapper").html(grid.getElement()); | |||
let $bg = $body.find(" .game > .bg"); | |||
return new Promise(async resolve => { | |||
sound.intro(); | |||
await sys.message({$body:$bg, msg:"모두 같은색으로 만드세요.", ms:3000}); | |||
for(let i = 3; i > 0; i--){ | |||
sound.count(); | |||
await sys.message({$body:$bg, msg:i, ms:500}); | |||
} | |||
sound.start(); | |||
bgm.play(); | |||
$bg.fadeOut(200); | |||
(async function(){ | |||
let index = 0; | |||
while(++index){ | |||
await grid.wait(); | |||
sound.success(); | |||
timer.addTime(TIME_ADDITONAL); | |||
grid = gridIter.next().value | |||
$body.find(".grid-wrapper").html(grid.getElement()); | |||
let musicIndex = MUSICS.findIndex(({step}) => step === index); | |||
if(musicIndex !== -1){ | |||
bgm.play(musicIndex); | |||
} | |||
} | |||
})(); | |||
timer.start(); | |||
await timer.wait(); | |||
let score = timer.getScore(); | |||
resolve({score}); | |||
}); | |||
}, | |||
result: ({score}) => { | |||
sound.result(); | |||
let {icon, message} = RATINGS.find(({limit}) => score < limit); | |||
pages.$body.html(` | |||
{div class="game result"} | |||
{div class="title"}{span class="rose"}게임 결과{/span}{/div} | |||
{div class="score"}${score.toFixed(0).toLocaleString()}점{/div} | |||
{div class="rating"} | |||
{span class="material-symbols-outlined icon"}${icon}{/span} | |||
{span class="message"}${message}{/span} | |||
{/div} | |||
{div class="btn-wrapper"} | |||
{div class="btn restart"}다시하기{/div} | |||
{div class="btn totalk"}토론{/div} | |||
{div class="btn rank"}랭킹등록{/div} | |||
{/div} | |||
{/div} | |||
`.parseHtml()); | |||
return new Promise(resolve => { | |||
pages.$body.find(".totalk").on("click", () => { | |||
let titleObj = new mw.Title(mw.config.get('wgPageName')); | |||
let talkPageUrl = titleObj.getTalkPage().getUrl(); | |||
location.href = talkPageUrl; | |||
}); | |||
pages.$body.find(".restart").on("click", () => { | |||
resolve(); | |||
}); | |||
pages.$body.find(".rank").on("click", async function(){ | |||
$(this).off("click").text("등록중..."); | |||
await sys.saveRanking({score}); | |||
$(this).text("등록완료"); | |||
}); | |||
}); | |||
}, | |||
} | |||
const sys = { | |||
init: async function() { | |||
$(".vector-sitenotice-container").hide(); | |||
let $body = $("#mw-content-text"); | |||
let bodyMinHeight = window.innerHeight - $body.offset().top - 100; | |||
$("#mw-content-text").addClass("gamebody") | |||
$body.css({ | |||
"height": bodyMinHeight, | |||
"display": "flex", | |||
"flex-direction": "column", | |||
"justify-content": "center", | |||
"position": "relative", | |||
}) | |||
$body.html(''); | |||
}, | |||
message: async function({$body, msg, ms, speed = 200} = {}) { | |||
let $msg = $("{div}{/div}".parseHtml()).text(msg).css({display:"none", textAlign:"center",fontSize:"1.5em"}); | |||
$body.append($msg); | |||
$msg.slideDown(speed/100); | |||
await util.asleep(ms + speed); | |||
$msg.slideUp(speed/100); | |||
await util.asleep(speed); | |||
$msg.remove(); | |||
}, | |||
initSound: (name) => { | |||
let thisSound = null | |||
repo.getFileUrl(`파일:${name}`).then(({url}) => thisSound = new Audio(url)); | |||
return () => { | |||
if(thisSound) thisSound.cloneNode().play(); | |||
} | |||
}, | |||
saveRanking: async function({score}) { | |||
let name = mw.user.getName() || "익명"; | |||
let rankingPagename = mw.config.get('wgPageName') + "/랭킹"; | |||
let content = await repo.getPage(rankingPagename); | |||
let prevRank = content === "" ? | |||
[] : | |||
content.trim().split("\n").map( row => { | |||
let [_,name,score] = row.match(/\* '''([^']+)'''[^\d]+(\d+)/) | |||
return [name,score] | |||
}) | |||
let rankIndex = prevRank.findIndex(([name,thisScore]) => score > thisScore); | |||
let rank = rankIndex === -1 ? prevRank.length + 1 : rankIndex + 1; | |||
let newRank = Object.entries(Object.fromEntries(prevRank.concat([[name,score]]))).sort((a,b) => b[1] - a[1]); | |||
let newRankText = newRank.map(([name,score]) => `* '''${name}''' : ${score}점`).join("\n"); | |||
let success = await repo.editPage({ | |||
pagename: rankingPagename, | |||
text: newRankText, | |||
summary: `랭킹 등록(${score}점, ${rank}위)`, | |||
}); | |||
mw.notify(success ? `${name}님의 점수가 등록되었습니다. (랭킹 ${rank}위)` : '랭킹 등록에 실패했습니다.'); | |||
return rank; | |||
} | } | ||
} | |||
const repo = { | |||
api: new mw.Api(), | |||
getFileUrl: async function (pagename) { | |||
let response = await repo.api.get({ | |||
action: 'query', | |||
titles: pagename, | |||
prop: 'imageinfo', | |||
iiprop: 'url', | |||
formatversion: 2 | |||
}); | |||
let pages = response.query.pages; | |||
let url = pages[0].imageinfo[0].url; | |||
return { url }; | |||
}, | |||
editPage: async function({pagename, text, summary, bot= true, minor = true} = {}) { | |||
let response = await repo.api.post({ | |||
action: 'edit', title: pagename, | |||
text, summary, bot, minor, | |||
token: await repo.api.getToken('csrf') | |||
}); | |||
if (response?.edit?.result === 'Success') { | |||
return true | |||
} else { | |||
return false; | |||
} | |||
}, | |||
getPage: async function({pagename}){ | |||
let response = await repo.api.get({ | |||
action: 'query', | |||
prop: 'revisions', | |||
rvprop: 'content', | |||
titles: pagename, | |||
formatversion: 2, | |||
}); | |||
if(response.query === undefined) return '' | |||
let pages = response.query.pages; | |||
let content = pages[0].revisions[0].content; | |||
return content | |||
} | } | ||
} | |||
const bgm = { | |||
audioEl: null, | |||
play: async function(level = 0) { | |||
let {url} = await repo.getFileUrl(MUSICS[level].pagename); | |||
bgm.stop(); | |||
this.audioEl = new Audio(url); | |||
this.currentTime = 0; | |||
this.audioEl.addEventListener('ended', function() { | |||
this.currentTime = 0; | |||
this.play(); | |||
}, false); | |||
this.audioEl.play(); | |||
}, | |||
stop: function() { | |||
if (this.audioEl) { | |||
this.audioEl.pause(); | |||
} | |||
} | } | ||
} | |||
class Timer{ | |||
constructor(limit){ | |||
this._limit = limit; | |||
this._time = 0; | |||
this._score = 0; | |||
this._$element = $(`{div class="timer"}{div class="bg"}{/div}{div class="remain"}${this._limit}{/div}{/div}`.parseHtml()); | |||
this._resolve = () => {}; | |||
} | } | ||
start(){ | |||
let $remain = this._$element.find(".remain"); | |||
let $bg = this._$element.find(".bg"); | |||
this._time = this._limit; | |||
this._score = 0; | |||
$remain.text(this._time); | |||
this._interval = setInterval(() => { | |||
this._time = Math.floor((this._time - 0.1) * 10) / 10; | |||
$remain.text(this._time); | |||
if(this._time <= 0){ | |||
clearInterval(this._interval); | |||
this._resolve(); | |||
} | |||
// time과 limt에 따라 $element의 width를 조정 | |||
let width = this._time / this._limit * 100; | |||
$bg.css({ | |||
width: `${width}%`, | |||
background: `hsl(${width * 1.2}, 100%, 80%)` | |||
}); | |||
}, 100); | |||
} | } | ||
addTime(amount){ | |||
this._time += amount; | |||
let diff = this._time - this._limit; | |||
this._score += diff; | |||
if(this._time > this._limit) this._time = this._limit; | |||
} | } | ||
wait(){ | |||
return new Promise(resolve => { | |||
this._resolve = resolve; | |||
}); | |||
} | } | ||
. | getScore(){ | ||
return Math.floor(this._score); | |||
} | |||
getElement(){ | |||
return this._$element; | |||
} | } | ||
} | |||
class Grid{ | |||
static DIRECTIONS = [[1,1],[1,0],[1,-1],[0,1],[0,0],[0,-1],[-1,1],[-1,0],[-1,-1]] | |||
constructor({row=5, col=5, depth=2, shuffleCount=2}={}){ | |||
this._row = row; this._col = col; this._depth = depth; this._resolve = () => {} | |||
this._grid = Array.from({length:row},()=>Array.from({length:col},()=>0)); | |||
this._shuffle(this._grid,shuffleCount); | |||
} | } | ||
static _isMobile(){ | |||
return /Android|webOS|iPhone|iPad|iPod|BlackBerry|IEMobile|Opera Mini/i.test(navigator.userAgent); | |||
} | } | ||
_sample(prevArr, count=1){ | |||
let result = []; | |||
let arr = [...prevArr]; | |||
for(let i=0;i<count;i++){ | |||
let index = Math.floor(Math.random()*arr.length); | |||
result.push(arr[index]); | |||
arr.splice(index,1); | |||
} | |||
return result; | |||
} | } | ||
. | _shuffle(grid,count){ | ||
let size = this._row * this._col; | |||
let depth = this._depth; | |||
let rawCells = Array.from({length:size},(_,i)=>i) | |||
rawCells = Array.from({length:depth - 1}, ()=>rawCells).flat(); | |||
let cells = this._sample(rawCells, count); | |||
for(let cell of cells){ | |||
let row = Math.floor(cell/this._col); | |||
let col = cell%this._col; | |||
this._move(grid,row,col); | |||
} | |||
} | } | ||
. | |||
_move(grid,row,col){ | |||
for(let [r,c] of Grid.DIRECTIONS){ | |||
let value = grid?.[row+r]?.[col+c]; | |||
if(value !== undefined){ | |||
grid[row+r][col+c] = ((grid[row+r][col+c] || 0) + 1) % this._depth; | |||
} | |||
} | |||
} | } | ||
_validate(grid){ | |||
return grid.every(row=>row.every(col=>col === grid[0][0])); | |||
} | } | ||
. | |||
getElement(){ | |||
let $grid = $("{div class='grid'}{/div}".parseHtml()).css({ | |||
display:"grid", width: "100%", height: "100%", gap: "8px", | |||
gridTemplateColumns: `repeat(${this._col},1fr)`, gridTemplateRows: `repeat(${this._row},1fr)`, | |||
}); | |||
let getColor = (row,col) => `hsl(${this._grid[row][col]*360/this._depth},100%,80%)`; | |||
for(let i = 0; i < this._row; i++){ | |||
for(let j = 0; j < this._col; j++){ | |||
let $div = $("{div class='cell'}{/div}".parseHtml()).css({ | |||
backgroundColor: getColor(i,j), | |||
}).attr({row:i,col:j}); | |||
$grid.append($div); | |||
} | |||
} | |||
$grid.find(".cell").on(Grid._isMobile()?"touchstart":"mousedown",e => { | |||
let $cell = $(e.currentTarget); | |||
let [row,col] = [$cell.attr("row"),$cell.attr("col")].map(v=>+v); | |||
this._move(this._grid,row,col); | |||
if(this._validate(this._grid)) this._resolve(); | |||
sound.move(); | |||
$cell.css({backgroundColor:getColor(row,col)}); // 현재 셀 색상 변경 | |||
setTimeout(()=>{ // 주변 셀 색상 변경 | |||
for(let [r,c] of Grid.DIRECTIONS){ | |||
let $cell = $grid.find(`.cell[row=${row+r}][col=${col+c}]`); | |||
if($cell.length) $cell.css({backgroundColor:getColor(row+r,col+c)}); | |||
} | |||
},100); | |||
}); | |||
return $grid; | |||
} | } | ||
wait(){ | |||
return new Promise(resolve=>{ | |||
this._resolve=resolve | |||
}); | |||
} | } | ||
} | |||
main(); | |||
2023년 8월 12일 (토) 15:13 판
/* --BANIP (토론) 2023년 8월 12일 (토) 03:30 (KST)
- /
const TIME_LIMIT = 60 const TIME_ADDITONAL = 20 const RATINGS = [ {icon:"battery_1_bar",message:"가능성만 있어요.",limit:100}, {icon:"battery_2_bar",message:"익히셨군요.",limit:200}, {icon:"battery_3_bar",message:"기대 이상이에요.",limit:300}, {icon:"battery_4_bar",message:"자랑해도 될 수준이에요.",limit:400}, {icon:"battery_5_bar",message:"익숙하시군요.",limit:500}, {icon:"battery_6_bar",message:"완벽에 가까워요.",limit:600}, {icon:"battery_full",message:"더이상 무엇을 바라겠어요?",limit:700}, ] const MUSICS = [ {pagename:"파일:Popcandy goorogi.mp3", step:0}, {pagename:"파일:Cyrf pluto.mp3", step:20}, {pagename:"파일:Isao blaze.mp3", step:40}, ]
const sound = { intro: sys.initSound("dodge_intro.mp3"), result: sys.initSound("dodge_result.mp3"), move: sys.initSound("dodge_move.mp3"), success: sys.initSound("dodge_success.mp3"), count: sys.initSound("dodge_count.mp3"), start: sys.initSound("dodge_start.mp3"), }
async function main() { await sys.init();
while(true){ bgm.stop(); await pages.lobby(); let {score} = await pages.game(); await pages.result({score}); } }
String.prototype.parseHtml = function(){ return this.replace(/{/g, "<").replace(/}/g, ">"); } const util = { asleep: async (ms) => { return new Promise(resolve => setTimeout(resolve, ms)); }, }
const pages = { $body: $("#mw-content-text"), lobby: () => { return new Promise(resolve => { pages.$body.html(` {div class="game lobby"} {div class="title"}{span class="skyblue"}컬러{/span} {span class="rose"}닷지{/span}{/div} {div class="btn start"}시작하기{/div} {/div} `.parseHtml()); //debugger let childSelectors = ".skyblue, .rose, .start"; pages.$body.find(childSelectors).css({display:"none"}); for( let [i, el] of Object.entries(pages.$body.find(childSelectors).toArray())){ if(!el) continue; setTimeout(() => { $(el).slideDown(500); }, i*500); }
pages.$body.find(".btn").on("click", async () => { sound.move(); pages.$body.find(childSelectors).fadeOut(500); await util.asleep(500); resolve(); }); }); }, game: () => { let $body = pages.$body; $body.html(` {div class="game play"} {div class="bg"}{/div} {div class="timer-wrapper"}{/div} {div class="grid-wrapper"}{/div} {/div} `.parseHtml()); let gridGen = function*(){ let inits = [0,1,4,2] let repeat = [5,4,2,Infinity] let now = [0,0,0,0] while(true){ let getValue = (i) => now[i] + inits[i] for(let i = 0; i < now.length; i++){ if(now[i] >= repeat[i]){ now[i] = 0; now[i+1] += 1;inits[i] += 1; } } yield new Grid({shuffleCount:getValue(1), row:getValue(2), col:getValue(2), depth:getValue(3)}) now[0] += 1;
} }
let gridIter = gridGen(); let timer = new Timer(TIME_LIMIT) let grid = gridIter.next().value $body.find(".timer-wrapper").html(timer.getElement()); $body.find(".grid-wrapper").html(grid.getElement());
let $bg = $body.find(" .game > .bg"); return new Promise(async resolve => { sound.intro(); await sys.message({$body:$bg, msg:"모두 같은색으로 만드세요.", ms:3000}); for(let i = 3; i > 0; i--){ sound.count(); await sys.message({$body:$bg, msg:i, ms:500}); } sound.start(); bgm.play(); $bg.fadeOut(200);
(async function(){ let index = 0; while(++index){ await grid.wait(); sound.success(); timer.addTime(TIME_ADDITONAL); grid = gridIter.next().value $body.find(".grid-wrapper").html(grid.getElement());
let musicIndex = MUSICS.findIndex(({step}) => step === index); if(musicIndex !== -1){ bgm.play(musicIndex); } } })(); timer.start(); await timer.wait(); let score = timer.getScore(); resolve({score}); }); }, result: ({score}) => { sound.result();
let {icon, message} = RATINGS.find(({limit}) => score < limit);
pages.$body.html(` {div class="game result"} {div class="title"}{span class="rose"}게임 결과{/span}{/div} {div class="score"}${score.toFixed(0).toLocaleString()}점{/div} {div class="rating"} {span class="material-symbols-outlined icon"}${icon}{/span} {span class="message"}${message}{/span} {/div} {div class="btn-wrapper"} {div class="btn restart"}다시하기{/div} {div class="btn totalk"}토론{/div} {div class="btn rank"}랭킹등록{/div} {/div} {/div} `.parseHtml());
return new Promise(resolve => {
pages.$body.find(".totalk").on("click", () => { let titleObj = new mw.Title(mw.config.get('wgPageName')); let talkPageUrl = titleObj.getTalkPage().getUrl(); location.href = talkPageUrl; }); pages.$body.find(".restart").on("click", () => { resolve(); }); pages.$body.find(".rank").on("click", async function(){ $(this).off("click").text("등록중..."); await sys.saveRanking({score}); $(this).text("등록완료"); }); }); }, }
const sys = { init: async function() { $(".vector-sitenotice-container").hide(); let $body = $("#mw-content-text"); let bodyMinHeight = window.innerHeight - $body.offset().top - 100; $("#mw-content-text").addClass("gamebody")
$body.css({ "height": bodyMinHeight, "display": "flex", "flex-direction": "column", "justify-content": "center", "position": "relative", }) $body.html(); }, message: async function({$body, msg, ms, speed = 200} = {}) { let $msg = $("{div}{/div}".parseHtml()).text(msg).css({display:"none", textAlign:"center",fontSize:"1.5em"}); $body.append($msg); $msg.slideDown(speed/100); await util.asleep(ms + speed); $msg.slideUp(speed/100); await util.asleep(speed); $msg.remove(); }, initSound: (name) => { let thisSound = null repo.getFileUrl(`파일:${name}`).then(({url}) => thisSound = new Audio(url)); return () => { if(thisSound) thisSound.cloneNode().play(); } }, saveRanking: async function({score}) { let name = mw.user.getName() || "익명"; let rankingPagename = mw.config.get('wgPageName') + "/랭킹"; let content = await repo.getPage(rankingPagename); let prevRank = content === "" ? [] : content.trim().split("\n").map( row => { let [_,name,score] = row.match(/\* ([^']+)[^\d]+(\d+)/) return [name,score] }) let rankIndex = prevRank.findIndex(([name,thisScore]) => score > thisScore); let rank = rankIndex === -1 ? prevRank.length + 1 : rankIndex + 1;
let newRank = Object.entries(Object.fromEntries(prevRank.concat(name,score))).sort((a,b) => b[1] - a[1]); let newRankText = newRank.map(([name,score]) => `* ${name} : ${score}점`).join("\n"); let success = await repo.editPage({ pagename: rankingPagename, text: newRankText,
summary: `랭킹 등록(${score}점, ${rank}위)`, }); mw.notify(success ? `${name}님의 점수가 등록되었습니다. (랭킹 ${rank}위)` : '랭킹 등록에 실패했습니다.'); return rank; } }
const repo = { api: new mw.Api(), getFileUrl: async function (pagename) { let response = await repo.api.get({ action: 'query', titles: pagename, prop: 'imageinfo', iiprop: 'url', formatversion: 2 }); let pages = response.query.pages; let url = pages[0].imageinfo[0].url; return { url }; }, editPage: async function({pagename, text, summary, bot= true, minor = true} = {}) {
let response = await repo.api.post({ action: 'edit', title: pagename, text, summary, bot, minor, token: await repo.api.getToken('csrf') });
if (response?.edit?.result === 'Success') { return true } else { return false; }
}, getPage: async function({pagename}){ let response = await repo.api.get({ action: 'query', prop: 'revisions', rvprop: 'content', titles: pagename, formatversion: 2, }); if(response.query === undefined) return let pages = response.query.pages; let content = pages[0].revisions[0].content;
return content } }
const bgm = { audioEl: null, play: async function(level = 0) { let {url} = await repo.getFileUrl(MUSICS[level].pagename); bgm.stop(); this.audioEl = new Audio(url); this.currentTime = 0; this.audioEl.addEventListener('ended', function() { this.currentTime = 0; this.play(); }, false);
this.audioEl.play(); }, stop: function() { if (this.audioEl) { this.audioEl.pause(); } } }
class Timer{
constructor(limit){
this._limit = limit; this._time = 0; this._score = 0; this._$element = $(`{div class="timer"}{div class="bg"}{/div}{div class="remain"}${this._limit}{/div}{/div}`.parseHtml()); this._resolve = () => {}; } start(){ let $remain = this._$element.find(".remain"); let $bg = this._$element.find(".bg"); this._time = this._limit; this._score = 0; $remain.text(this._time); this._interval = setInterval(() => { this._time = Math.floor((this._time - 0.1) * 10) / 10; $remain.text(this._time); if(this._time <= 0){ clearInterval(this._interval); this._resolve(); }
// time과 limt에 따라 $element의 width를 조정 let width = this._time / this._limit * 100; $bg.css({ width: `${width}%`, background: `hsl(${width * 1.2}, 100%, 80%)` });
}, 100); }
addTime(amount){ this._time += amount; let diff = this._time - this._limit; this._score += diff; if(this._time > this._limit) this._time = this._limit; } wait(){ return new Promise(resolve => { this._resolve = resolve; });
} getScore(){ return Math.floor(this._score); } getElement(){ return this._$element; } }
class Grid{ static DIRECTIONS = [[1,1],[1,0],[1,-1],[0,1],[0,0],[0,-1],[-1,1],[-1,0],[-1,-1]] constructor({row=5, col=5, depth=2, shuffleCount=2}={}){ this._row = row; this._col = col; this._depth = depth; this._resolve = () => {} this._grid = Array.from({length:row},()=>Array.from({length:col},()=>0)); this._shuffle(this._grid,shuffleCount); }
static _isMobile(){ return /Android|webOS|iPhone|iPad|iPod|BlackBerry|IEMobile|Opera Mini/i.test(navigator.userAgent); }
_sample(prevArr, count=1){ let result = []; let arr = [...prevArr]; for(let i=0;i<count;i++){ let index = Math.floor(Math.random()*arr.length); result.push(arr[index]); arr.splice(index,1); } return result; }
_shuffle(grid,count){ let size = this._row * this._col; let depth = this._depth; let rawCells = Array.from({length:size},(_,i)=>i) rawCells = Array.from({length:depth - 1}, ()=>rawCells).flat();
let cells = this._sample(rawCells, count);
for(let cell of cells){ let row = Math.floor(cell/this._col); let col = cell%this._col; this._move(grid,row,col); } }
_move(grid,row,col){ for(let [r,c] of Grid.DIRECTIONS){ let value = grid?.[row+r]?.[col+c]; if(value !== undefined){ grid[row+r][col+c] = ((grid[row+r][col+c] || 0) + 1) % this._depth; } } }
_validate(grid){ return grid.every(row=>row.every(col=>col === grid[0][0])); }
getElement(){ let $grid = $("{div class='grid'}{/div}".parseHtml()).css({ display:"grid", width: "100%", height: "100%", gap: "8px", gridTemplateColumns: `repeat(${this._col},1fr)`, gridTemplateRows: `repeat(${this._row},1fr)`, }); let getColor = (row,col) => `hsl(${this._grid[row][col]*360/this._depth},100%,80%)`; for(let i = 0; i < this._row; i++){ for(let j = 0; j < this._col; j++){ let $div = $("{div class='cell'}{/div}".parseHtml()).css({ backgroundColor: getColor(i,j), }).attr({row:i,col:j}); $grid.append($div); } }
$grid.find(".cell").on(Grid._isMobile()?"touchstart":"mousedown",e => { let $cell = $(e.currentTarget); let [row,col] = [$cell.attr("row"),$cell.attr("col")].map(v=>+v); this._move(this._grid,row,col); if(this._validate(this._grid)) this._resolve(); sound.move();
$cell.css({backgroundColor:getColor(row,col)}); // 현재 셀 색상 변경 setTimeout(()=>{ // 주변 셀 색상 변경 for(let [r,c] of Grid.DIRECTIONS){ let $cell = $grid.find(`.cell[row=${row+r}][col=${col+c}]`); if($cell.length) $cell.css({backgroundColor:getColor(row+r,col+c)}); } },100); });
return $grid;
}
wait(){ return new Promise(resolve=>{ this._resolve=resolve }); } }
main();