컬러닷지/스크립트: 두 판 사이의 차이
보이기
< 컬러닷지
태그: 일괄 되돌리기 되돌려진 기여 |
잔글편집 요약 없음 태그: 수동 되돌리기 |
||
469번째 줄: | 469번째 줄: | ||
} | } | ||
main | $(main); |
2023년 9월 3일 (일) 16:02 기준 최신판
/*
--[[사용자: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:500},
{icon:"battery_5_bar",message:"익숙하시군요.",limit:700},
{icon:"battery_6_bar",message:"완벽에 가까워요.",limit:1000},
{icon:"battery_full",message:"더이상 무엇을 바라겠어요?",limit:Infinity},
]
const MUSICS = [
{pagename:"파일:Popcandy goorogi.mp3", step:0},
{pagename:"파일:Cyrf pluto.mp3", step:40},
{pagename:"파일:Isao blaze.mp3", step:80},
]
async function main() {
console.log('color dodge 1.0v')
await sys.init();
while(true){
bgm.stop();
await pages.lobby();
let {score} = await pages.game();
await pages.result({score});
}
}
const sys = {
init: async function() {
$(".vector-sitenotice-container").hide();
let $body = $("#mw-content-text");
let bodyMinHeight = window.innerHeight - $body.offset().top - 50;
$("#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() || ("익명" + Math.floor(Math.random()*100000).toString().padStart(5,'0'))
let rankingPagename = mw.config.get('wgPageName') + "/랭킹";
let content = await repo.getPage({pagename:rankingPagename});
let regex = /'{3}([^']+)'{3}[^\d]+(\d+)/
let prevRank = (content || "") === "" ?
[] :
content.trim().split("\n").filter(v => v.match(regex)).map( row => {
let [_,name,score] = row.match(regex)
return [name,score]
})
let userPrevRank = prevRank.find(([prevName,_]) => prevName === name);
let userPrevScore = userPrevRank ? parseInt(userPrevRank[1]) : 0;
if(score <= userPrevScore) {
mw.notify('이전 점수보다 높지 않아 랭킹에 등록되지 않았습니다. (이전 점수: ' + userPrevScore + '점)');
return;
}
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 bold = `'`.repeat(3);
let newRankText = newRank.map(([name,score]) => `* ${bold}${name}${bold} : ${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 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"),
}
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 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);