|
|
|
|
<template>
|
|
|
|
|
<view class="boost">
|
|
|
|
|
<view class="nav" :style="{paddingTop: statusBarHeight + 'px'}">
|
|
|
|
|
<view class="nav-back" @tap="goBack"><text>‹</text></view>
|
|
|
|
|
<view class="nav-title">学院助推赛</view>
|
|
|
|
|
</view>
|
|
|
|
|
|
|
|
|
|
<scroll-view scroll-y class="page" :style="{paddingTop: (statusBarHeight + 58) + 'px'}">
|
|
|
|
|
<view class="hero">
|
|
|
|
|
<view class="hero-kicker">COLLEGE ROCKET</view>
|
|
|
|
|
<view class="hero-title">今天为 {{collegeName}} 推一把</view>
|
|
|
|
|
<view class="college-picker" @tap="openCollegePicker">
|
|
|
|
|
<text>{{college ? '已选择学院' : '先选择学院'}}</text>
|
|
|
|
|
<text>{{collegeName}} ›</text>
|
|
|
|
|
</view>
|
|
|
|
|
<view class="rocket">
|
|
|
|
|
<view class="rocket-body">🚀</view>
|
|
|
|
|
<view class="rocket-line">
|
|
|
|
|
<view class="rocket-progress" :style="{width: rocketPercent + '%'}"></view>
|
|
|
|
|
</view>
|
|
|
|
|
</view>
|
|
|
|
|
<view class="hero-sub">每天1关,通关贡献学院推进值。21:30结算,前三学院成员分别获得3/2/1张星球券。</view>
|
|
|
|
|
</view>
|
|
|
|
|
|
|
|
|
|
<view class="rank-card">
|
|
|
|
|
<view class="section-head">
|
|
|
|
|
<text>学院推进榜</text>
|
|
|
|
|
<text>我的学院第 {{home.myRankNo || '-'}} 名</text>
|
|
|
|
|
</view>
|
|
|
|
|
<view class="college-row" v-for="(item,i) in collegeRanks" :key="i">
|
|
|
|
|
<view class="medal">{{i + 1}}</view>
|
|
|
|
|
<view class="college-main">
|
|
|
|
|
<text>{{item.college || item.nickname || '未知学院'}}</text>
|
|
|
|
|
<view class="bar"><view :style="{width: (item.percent || 8) + '%'}"></view></view>
|
|
|
|
|
</view>
|
|
|
|
|
<view class="score">{{item.score || 0}}</view>
|
|
|
|
|
</view>
|
|
|
|
|
<view class="empty" v-if="!collegeRanks.length">今天还没有学院上榜,快来抢第一棒</view>
|
|
|
|
|
</view>
|
|
|
|
|
|
|
|
|
|
<view class="game-card">
|
|
|
|
|
<view class="section-head">
|
|
|
|
|
<text>今日关卡</text>
|
|
|
|
|
<text>{{cleared ? '已通关' : '约2分钟'}}</text>
|
|
|
|
|
</view>
|
|
|
|
|
|
|
|
|
|
<view class="tips">
|
|
|
|
|
<text>规则:点选未被压住的卡牌,底部槽内3张相同自动消除,槽满则失败。</text>
|
|
|
|
|
<text>剩余 {{remainingCount}} 张 · 当前可点 {{availableCount}} 张 · {{riskText}}</text>
|
|
|
|
|
</view>
|
|
|
|
|
|
|
|
|
|
<view class="board">
|
|
|
|
|
<view
|
|
|
|
|
class="tile"
|
|
|
|
|
v-for="card in visibleCards"
|
|
|
|
|
:key="card.id"
|
|
|
|
|
:class="{locked: isLocked(card), selected: card.selected}"
|
|
|
|
|
:style="card.style"
|
|
|
|
|
@tap="pickCard(card)">
|
|
|
|
|
<text>{{card.icon}}</text>
|
|
|
|
|
</view>
|
|
|
|
|
</view>
|
|
|
|
|
|
|
|
|
|
<view class="slot-wrap">
|
|
|
|
|
<view class="slot-title">助推槽 {{slots.length}}/7</view>
|
|
|
|
|
<view class="slots">
|
|
|
|
|
<view class="slot" v-for="i in 7" :key="i">
|
|
|
|
|
<text v-if="slots[i - 1]">{{slots[i - 1].icon}}</text>
|
|
|
|
|
</view>
|
|
|
|
|
</view>
|
|
|
|
|
</view>
|
|
|
|
|
|
|
|
|
|
<view class="actions">
|
|
|
|
|
<view class="sub-btn" @tap="resetLevel">重开本关</view>
|
|
|
|
|
<view class="main-btn" :class="{disabled: cleared || playing}" @tap="startLevel">
|
|
|
|
|
{{cleared ? '今日已助推' : (session ? '继续助推' : '开始助推')}}
|
|
|
|
|
</view>
|
|
|
|
|
</view>
|
|
|
|
|
</view>
|
|
|
|
|
</scroll-view>
|
|
|
|
|
|
|
|
|
|
<view v-if="modal.show" class="modal">
|
|
|
|
|
<view class="modal-card">
|
|
|
|
|
<view class="modal-title">{{modal.title}}</view>
|
|
|
|
|
<view class="modal-sub">{{modal.sub}}</view>
|
|
|
|
|
<view class="main-btn" @tap="closeModal">知道了</view>
|
|
|
|
|
</view>
|
|
|
|
|
</view>
|
|
|
|
|
|
|
|
|
|
<view v-if="collegeModal" class="modal">
|
|
|
|
|
<view class="modal-card college-modal">
|
|
|
|
|
<view class="modal-title">选择你的学院</view>
|
|
|
|
|
<view class="modal-sub">学院来自当前校区后台配置,可随时修改。当天通关后会为所选学院贡献推进值。</view>
|
|
|
|
|
<scroll-view scroll-y class="college-list">
|
|
|
|
|
<view
|
|
|
|
|
class="college-option"
|
|
|
|
|
v-for="(item,i) in colleges"
|
|
|
|
|
:key="i"
|
|
|
|
|
:class="{active: college === item.collegeName}"
|
|
|
|
|
@tap="selectCollege(item)">
|
|
|
|
|
<text>{{item.icon || '🎓'}}</text>
|
|
|
|
|
<view>
|
|
|
|
|
<text>{{item.collegeName}}</text>
|
|
|
|
|
<text>{{item.shortName || '学院战队'}}</text>
|
|
|
|
|
</view>
|
|
|
|
|
</view>
|
|
|
|
|
</scroll-view>
|
|
|
|
|
<view class="empty" v-if="!colleges.length">当前校区还没有配置学院,请联系后台先维护学院配置</view>
|
|
|
|
|
<view class="main-btn" @tap="closeCollegePicker">确定</view>
|
|
|
|
|
</view>
|
|
|
|
|
</view>
|
|
|
|
|
</view>
|
|
|
|
|
</template>
|
|
|
|
|
|
|
|
|
|
<script>
|
|
|
|
|
export default {
|
|
|
|
|
data() {
|
|
|
|
|
return {
|
|
|
|
|
statusBarHeight: 20,
|
|
|
|
|
userId: '',
|
|
|
|
|
regionId: '',
|
|
|
|
|
nickname: '',
|
|
|
|
|
avatar: '',
|
|
|
|
|
college: '',
|
|
|
|
|
colleges: [],
|
|
|
|
|
collegeModal: false,
|
|
|
|
|
home: {},
|
|
|
|
|
session: null,
|
|
|
|
|
playing: false,
|
|
|
|
|
cleared: false,
|
|
|
|
|
startTs: 0,
|
|
|
|
|
cards: [],
|
|
|
|
|
slots: [],
|
|
|
|
|
moveCount: 0,
|
|
|
|
|
modal: { show: false, title: '', sub: '' }
|
|
|
|
|
}
|
|
|
|
|
},
|
|
|
|
|
computed: {
|
|
|
|
|
collegeName() {
|
|
|
|
|
return this.college || '我的学院'
|
|
|
|
|
},
|
|
|
|
|
collegeRanks() {
|
|
|
|
|
const list = this.home.rankList || []
|
|
|
|
|
let max = 1
|
|
|
|
|
list.forEach(item => {
|
|
|
|
|
if ((item.score || 0) > max) max = item.score || 0
|
|
|
|
|
})
|
|
|
|
|
return list.map(item => {
|
|
|
|
|
item.percent = Math.max(8, Math.round((item.score || 0) * 100 / max))
|
|
|
|
|
return item
|
|
|
|
|
})
|
|
|
|
|
},
|
|
|
|
|
rocketPercent() {
|
|
|
|
|
let mine = null
|
|
|
|
|
for (let i = 0; i < this.collegeRanks.length; i++) {
|
|
|
|
|
const item = this.collegeRanks[i]
|
|
|
|
|
if ((item.college || item.nickname) === this.college) {
|
|
|
|
|
mine = item
|
|
|
|
|
break
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
const top = this.collegeRanks[0] || {}
|
|
|
|
|
if (!mine || !top.score) return 16
|
|
|
|
|
return Math.max(16, Math.min(100, Math.round((mine.score || 0) * 100 / top.score)))
|
|
|
|
|
},
|
|
|
|
|
visibleCards() {
|
|
|
|
|
return this.cards.filter(item => !item.removed)
|
|
|
|
|
},
|
|
|
|
|
remainingCount() {
|
|
|
|
|
return this.cards.filter(item => !item.removed && !item.selected).length
|
|
|
|
|
},
|
|
|
|
|
availableCount() {
|
|
|
|
|
return this.cards.filter(item => !item.removed && !item.selected && !this.isLocked(item)).length
|
|
|
|
|
},
|
|
|
|
|
riskText() {
|
|
|
|
|
if (this.slots.length >= 6) return '危险:差1格就满'
|
|
|
|
|
if (this.slots.length >= 5) return '注意槽位'
|
|
|
|
|
return '先看上层再下手'
|
|
|
|
|
}
|
|
|
|
|
},
|
|
|
|
|
onLoad() {
|
|
|
|
|
const sys = uni.getSystemInfoSync()
|
|
|
|
|
this.statusBarHeight = sys.statusBarHeight || 20
|
|
|
|
|
this.userId = uni.getStorageSync('id') || ''
|
|
|
|
|
this.nickname = uni.getStorageSync('nickName') || uni.getStorageSync('nickname') || ''
|
|
|
|
|
this.avatar = uni.getStorageSync('avatarUrl') || uni.getStorageSync('avatar') || ''
|
|
|
|
|
try {
|
|
|
|
|
const area = uni.getStorageSync('area')
|
|
|
|
|
if (area) this.regionId = JSON.parse(area).id || ''
|
|
|
|
|
} catch (e) {}
|
|
|
|
|
this.college = uni.getStorageSync('planetCollege_' + this.regionId) || ''
|
|
|
|
|
this.loadColleges()
|
|
|
|
|
this.loadHome()
|
|
|
|
|
this.buildLevel()
|
|
|
|
|
},
|
|
|
|
|
methods: {
|
|
|
|
|
loadColleges() {
|
|
|
|
|
this.tui.request('/app/planet/college/list', 'POST', {
|
|
|
|
|
regionId: this.regionId
|
|
|
|
|
}, false, false, true).then(res => {
|
|
|
|
|
if (res.code == 200) {
|
|
|
|
|
this.colleges = res.result || []
|
|
|
|
|
const exists = this.colleges.some(item => item.collegeName === this.college)
|
|
|
|
|
if (!exists) {
|
|
|
|
|
this.college = ''
|
|
|
|
|
uni.removeStorageSync('planetCollege_' + this.regionId)
|
|
|
|
|
}
|
|
|
|
|
if (!this.college && this.colleges.length) {
|
|
|
|
|
this.collegeModal = true
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
})
|
|
|
|
|
},
|
|
|
|
|
loadHome() {
|
|
|
|
|
this.tui.request('/app/planet/adventure/home', 'POST', {
|
|
|
|
|
userId: this.userId,
|
|
|
|
|
regionId: this.regionId,
|
|
|
|
|
college: this.college
|
|
|
|
|
}, false, false, true).then(res => {
|
|
|
|
|
if (res.code == 200 && res.result) {
|
|
|
|
|
this.home = res.result
|
|
|
|
|
this.cleared = !res.result.freeAvailable
|
|
|
|
|
}
|
|
|
|
|
})
|
|
|
|
|
},
|
|
|
|
|
startLevel() {
|
|
|
|
|
if (this.cleared || this.playing) return
|
|
|
|
|
if (!this.college) {
|
|
|
|
|
this.openCollegePicker()
|
|
|
|
|
return
|
|
|
|
|
}
|
|
|
|
|
this.tui.request('/app/planet/adventure/start', 'POST', {
|
|
|
|
|
userId: this.userId,
|
|
|
|
|
regionId: this.regionId,
|
|
|
|
|
nickname: this.nickname,
|
|
|
|
|
avatar: this.avatar,
|
|
|
|
|
college: this.college
|
|
|
|
|
}).then(res => {
|
|
|
|
|
if (res.code != 200 || !res.result) {
|
|
|
|
|
this.tui.toast(res.message)
|
|
|
|
|
return
|
|
|
|
|
}
|
|
|
|
|
this.session = res.result
|
|
|
|
|
this.playing = true
|
|
|
|
|
this.startTs = Date.now()
|
|
|
|
|
this.buildLevel()
|
|
|
|
|
})
|
|
|
|
|
},
|
|
|
|
|
buildLevel() {
|
|
|
|
|
const icons = ['🍔', '🥤', '📚', '🏀', '🎧', '🚲', '🍜', '📦', '☕']
|
|
|
|
|
let cards = []
|
|
|
|
|
for (let attempt = 0; attempt < 8; attempt++) {
|
|
|
|
|
const seed = this.levelSeed() + '_try_' + attempt
|
|
|
|
|
const positions = this.buildCardPositions(seed)
|
|
|
|
|
cards = this.assignSolvableIcons(positions, icons, seed)
|
|
|
|
|
if (cards.length) break
|
|
|
|
|
}
|
|
|
|
|
if (!cards.length) {
|
|
|
|
|
cards = this.assignSolvableIcons(this.buildFallbackPositions(), icons, this.levelSeed() + '_fallback')
|
|
|
|
|
}
|
|
|
|
|
this.cards = cards
|
|
|
|
|
this.slots = []
|
|
|
|
|
this.moveCount = 0
|
|
|
|
|
},
|
|
|
|
|
buildCardPositions(seed) {
|
|
|
|
|
const cards = []
|
|
|
|
|
const layerCounts = [30, 27, 24]
|
|
|
|
|
const rand = this.seededRandom(seed + '_pos')
|
|
|
|
|
let idx = 0
|
|
|
|
|
layerCounts.forEach((count, layer) => {
|
|
|
|
|
for (let i = 0; i < count; i++) {
|
|
|
|
|
const col = i % 6
|
|
|
|
|
const row = Math.floor(i / 6)
|
|
|
|
|
const dx = Math.floor(rand() * 22)
|
|
|
|
|
const dy = Math.floor(rand() * 18)
|
|
|
|
|
const card = {
|
|
|
|
|
id: 'c' + idx,
|
|
|
|
|
icon: '',
|
|
|
|
|
layer,
|
|
|
|
|
x: 34 + col * 96 + (layer * 24) + ((row % 2) * 18) + dx,
|
|
|
|
|
y: 24 + row * 78 + (layer * 44) + dy,
|
|
|
|
|
style: '',
|
|
|
|
|
removed: false,
|
|
|
|
|
selected: false,
|
|
|
|
|
solutionStep: 0
|
|
|
|
|
}
|
|
|
|
|
card.style = `left:${card.x}rpx;top:${card.y}rpx;z-index:${card.layer + 1};`
|
|
|
|
|
cards.push(card)
|
|
|
|
|
idx++
|
|
|
|
|
}
|
|
|
|
|
})
|
|
|
|
|
return cards
|
|
|
|
|
},
|
|
|
|
|
assignSolvableIcons(positions, icons, seed) {
|
|
|
|
|
// 上线关卡不能随机发牌。这里先模拟一条真实可点击的消除路径,
|
|
|
|
|
// 再按路径给三张牌分配同一图案,保证每日关卡至少存在一条解法。
|
|
|
|
|
const rand = this.seededRandom(seed + '_solution')
|
|
|
|
|
const remaining = positions.slice()
|
|
|
|
|
const solutionGroups = []
|
|
|
|
|
while (remaining.length) {
|
|
|
|
|
const available = remaining.filter(card => !this.isBlockedIn(card, remaining))
|
|
|
|
|
if (available.length < 3) return []
|
|
|
|
|
const picked = this.pickSolutionTriple(available, rand)
|
|
|
|
|
solutionGroups.push(picked)
|
|
|
|
|
picked.forEach(card => {
|
|
|
|
|
const idx = remaining.indexOf(card)
|
|
|
|
|
if (idx >= 0) remaining.splice(idx, 1)
|
|
|
|
|
})
|
|
|
|
|
}
|
|
|
|
|
const tripleIcons = []
|
|
|
|
|
icons.forEach(icon => {
|
|
|
|
|
for (let i = 0; i < 3; i++) tripleIcons.push(icon)
|
|
|
|
|
})
|
|
|
|
|
this.seededShuffle(tripleIcons, seed + '_icons')
|
|
|
|
|
solutionGroups.forEach((group, step) => {
|
|
|
|
|
const icon = tripleIcons[step]
|
|
|
|
|
group.forEach(card => {
|
|
|
|
|
card.icon = icon
|
|
|
|
|
card.solutionStep = step + 1
|
|
|
|
|
})
|
|
|
|
|
})
|
|
|
|
|
return positions
|
|
|
|
|
},
|
|
|
|
|
pickSolutionTriple(available, rand) {
|
|
|
|
|
const pool = available.slice()
|
|
|
|
|
this.shuffleWithRandom(pool, rand)
|
|
|
|
|
const first = pool.shift()
|
|
|
|
|
pool.sort((a, b) => this.cardDistanceScore(b, first) - this.cardDistanceScore(a, first))
|
|
|
|
|
const second = pool.shift()
|
|
|
|
|
pool.sort((a, b) => {
|
|
|
|
|
const bd = this.cardDistanceScore(b, first) + this.cardDistanceScore(b, second)
|
|
|
|
|
const ad = this.cardDistanceScore(a, first) + this.cardDistanceScore(a, second)
|
|
|
|
|
return bd - ad
|
|
|
|
|
})
|
|
|
|
|
const third = pool.shift()
|
|
|
|
|
return [first, second, third]
|
|
|
|
|
},
|
|
|
|
|
cardDistanceScore(a, b) {
|
|
|
|
|
return Math.abs(a.x - b.x) + Math.abs(a.y - b.y) + Math.abs(a.layer - b.layer) * 60
|
|
|
|
|
},
|
|
|
|
|
isBlockedIn(card, list) {
|
|
|
|
|
return list.some(other => {
|
|
|
|
|
if (other.id === card.id || other.layer <= card.layer) return false
|
|
|
|
|
return this.isOverlap(card, other)
|
|
|
|
|
})
|
|
|
|
|
},
|
|
|
|
|
isOverlap(a, b) {
|
|
|
|
|
return Math.abs(a.x - b.x) < 78 && Math.abs(a.y - b.y) < 70
|
|
|
|
|
},
|
|
|
|
|
pickCard(card) {
|
|
|
|
|
if (!this.playing) {
|
|
|
|
|
this.tui.toast('先点击开始助推')
|
|
|
|
|
return
|
|
|
|
|
}
|
|
|
|
|
if (card.removed || card.selected || this.isLocked(card)) return
|
|
|
|
|
if (this.slots.length >= 7) return
|
|
|
|
|
card.selected = true
|
|
|
|
|
this.slots.push(card)
|
|
|
|
|
this.moveCount++
|
|
|
|
|
this.tryClear(card.icon)
|
|
|
|
|
if (this.cards.every(item => item.removed || item.selected)) {
|
|
|
|
|
this.finishClear()
|
|
|
|
|
} else if (this.slots.length >= 7) {
|
|
|
|
|
this.failLevel()
|
|
|
|
|
}
|
|
|
|
|
},
|
|
|
|
|
tryClear(icon) {
|
|
|
|
|
const same = this.slots.filter(item => item.icon === icon)
|
|
|
|
|
if (same.length < 3) return
|
|
|
|
|
let removed = 0
|
|
|
|
|
this.slots = this.slots.filter(item => {
|
|
|
|
|
if (item.icon === icon && removed < 3) {
|
|
|
|
|
item.removed = true
|
|
|
|
|
removed++
|
|
|
|
|
return false
|
|
|
|
|
}
|
|
|
|
|
return true
|
|
|
|
|
})
|
|
|
|
|
},
|
|
|
|
|
isLocked(card) {
|
|
|
|
|
return this.cards.some(other => {
|
|
|
|
|
if (other.removed || other.selected || other.layer <= card.layer) return false
|
|
|
|
|
return this.isOverlap(card, other)
|
|
|
|
|
})
|
|
|
|
|
},
|
|
|
|
|
finishClear() {
|
|
|
|
|
const duration = Math.max(20, Math.floor((Date.now() - this.startTs) / 1000))
|
|
|
|
|
const progress = Math.max(100, 260 - duration + Math.max(0, 80 - this.moveCount))
|
|
|
|
|
this.playing = false
|
|
|
|
|
this.tui.request('/app/planet/adventure/submit', 'POST', {
|
|
|
|
|
userId: this.userId,
|
|
|
|
|
regionId: this.regionId,
|
|
|
|
|
sessionId: this.session.sessionId,
|
|
|
|
|
normalCount: this.cards.length,
|
|
|
|
|
goldenCount: 1,
|
|
|
|
|
diamondCount: 0,
|
|
|
|
|
rainbowCount: 0,
|
|
|
|
|
ticketBagCount: 0,
|
|
|
|
|
propUseCount: 0,
|
|
|
|
|
hitCount: 0,
|
|
|
|
|
score: progress,
|
|
|
|
|
durationSeconds: duration
|
|
|
|
|
}).then(res => {
|
|
|
|
|
if (res.code == 200) {
|
|
|
|
|
this.cleared = true
|
|
|
|
|
this.modal = { show: true, title: '助推成功', sub: `${this.collegeName} 获得 ${progress} 点推进值,晚上21:30争夺学院荣誉。` }
|
|
|
|
|
this.loadHome()
|
|
|
|
|
} else {
|
|
|
|
|
this.tui.toast(res.message)
|
|
|
|
|
}
|
|
|
|
|
})
|
|
|
|
|
},
|
|
|
|
|
failLevel() {
|
|
|
|
|
this.playing = false
|
|
|
|
|
this.modal = { show: true, title: '槽位满了', sub: '本关失败,不消耗次数。换个顺序再推一次。' }
|
|
|
|
|
},
|
|
|
|
|
resetLevel() {
|
|
|
|
|
if (this.cleared) return
|
|
|
|
|
this.playing = false
|
|
|
|
|
this.buildLevel()
|
|
|
|
|
},
|
|
|
|
|
closeModal() {
|
|
|
|
|
this.modal.show = false
|
|
|
|
|
},
|
|
|
|
|
openCollegePicker() {
|
|
|
|
|
this.collegeModal = true
|
|
|
|
|
},
|
|
|
|
|
closeCollegePicker() {
|
|
|
|
|
if (!this.college) {
|
|
|
|
|
this.tui.toast('请先选择学院')
|
|
|
|
|
return
|
|
|
|
|
}
|
|
|
|
|
this.collegeModal = false
|
|
|
|
|
this.loadHome()
|
|
|
|
|
},
|
|
|
|
|
selectCollege(item) {
|
|
|
|
|
this.college = item.collegeName
|
|
|
|
|
uni.setStorageSync('planetCollege_' + this.regionId, this.college)
|
|
|
|
|
},
|
|
|
|
|
levelSeed() {
|
|
|
|
|
const d = new Date()
|
|
|
|
|
const p = n => (n < 10 ? '0' + n : n)
|
|
|
|
|
return `${this.regionId}_${d.getFullYear()}${p(d.getMonth() + 1)}${p(d.getDate())}_college_boost_v2`
|
|
|
|
|
},
|
|
|
|
|
seededRandom(seed) {
|
|
|
|
|
let h = 2166136261
|
|
|
|
|
for (let i = 0; i < seed.length; i++) {
|
|
|
|
|
h ^= seed.charCodeAt(i)
|
|
|
|
|
h += (h << 1) + (h << 4) + (h << 7) + (h << 8) + (h << 24)
|
|
|
|
|
}
|
|
|
|
|
return function() {
|
|
|
|
|
h += 0x6D2B79F5
|
|
|
|
|
let t = h
|
|
|
|
|
t = Math.imul(t ^ (t >>> 15), t | 1)
|
|
|
|
|
t ^= t + Math.imul(t ^ (t >>> 7), t | 61)
|
|
|
|
|
return ((t ^ (t >>> 14)) >>> 0) / 4294967296
|
|
|
|
|
}
|
|
|
|
|
},
|
|
|
|
|
seededShuffle(arr, seed) {
|
|
|
|
|
const rand = this.seededRandom(seed)
|
|
|
|
|
return this.shuffleWithRandom(arr, rand)
|
|
|
|
|
},
|
|
|
|
|
shuffleWithRandom(arr, rand) {
|
|
|
|
|
for (let i = arr.length - 1; i > 0; i--) {
|
|
|
|
|
const j = Math.floor(rand() * (i + 1))
|
|
|
|
|
const t = arr[i]
|
|
|
|
|
arr[i] = arr[j]
|
|
|
|
|
arr[j] = t
|
|
|
|
|
}
|
|
|
|
|
return arr
|
|
|
|
|
},
|
|
|
|
|
buildFallbackPositions() {
|
|
|
|
|
const cards = []
|
|
|
|
|
let idx = 0
|
|
|
|
|
for (let layer = 0; layer < 3; layer++) {
|
|
|
|
|
for (let i = 0; i < 27; i++) {
|
|
|
|
|
const col = i % 9
|
|
|
|
|
const row = Math.floor(i / 9)
|
|
|
|
|
const card = {
|
|
|
|
|
id: 'f' + idx,
|
|
|
|
|
icon: '',
|
|
|
|
|
layer,
|
|
|
|
|
x: 18 + col * 76 + (layer * 8),
|
|
|
|
|
y: 28 + row * 94 + (layer * 34),
|
|
|
|
|
style: '',
|
|
|
|
|
removed: false,
|
|
|
|
|
selected: false,
|
|
|
|
|
solutionStep: 0
|
|
|
|
|
}
|
|
|
|
|
card.style = `left:${card.x}rpx;top:${card.y}rpx;z-index:${card.layer + 1};`
|
|
|
|
|
cards.push(card)
|
|
|
|
|
idx++
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
return cards
|
|
|
|
|
},
|
|
|
|
|
goBack() {
|
|
|
|
|
uni.navigateBack()
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
</script>
|
|
|
|
|
|
|
|
|
|
<style lang="scss" scoped>
|
|
|
|
|
.boost { min-height: 100vh; background: linear-gradient(155deg, #F3FFF4 0%, #EAF8FF 46%, #FFF7DE 100%); color: #12342F; }
|
|
|
|
|
.nav { position: fixed; top: 0; left: 0; right: 0; height: 44px; z-index: 20; display: flex; align-items: flex-end; justify-content: center; padding-bottom: 8rpx; background: rgba(243,255,244,0.86); }
|
|
|
|
|
.nav-back { position: absolute; left: 20rpx; bottom: 0; width: 70rpx; height: 44px; display: flex; align-items: center; justify-content: center; color: #12342F; font-size: 54rpx; }
|
|
|
|
|
.nav-title { color: #12342F; font-size: 34rpx; font-weight: 900; }
|
|
|
|
|
.page { height: 100vh; box-sizing: border-box; padding-left: 24rpx; padding-right: 24rpx; }
|
|
|
|
|
.hero, .rank-card, .game-card { margin-bottom: 24rpx; border-radius: 42rpx; background: rgba(255,255,255,0.82); padding: 30rpx; box-shadow: 0 16rpx 42rpx rgba(79,183,255,0.10); }
|
|
|
|
|
.hero-kicker { color: #FF9C42; font-size: 22rpx; font-weight: 900; letter-spacing: 2rpx; }
|
|
|
|
|
.hero-title { margin-top: 10rpx; font-size: 42rpx; font-weight: 900; }
|
|
|
|
|
.hero-sub, .tips { margin-top: 16rpx; color: #6B817D; font-size: 24rpx; line-height: 1.55; }
|
|
|
|
|
.tips { display: flex; flex-direction: column; gap: 6rpx; }
|
|
|
|
|
.college-picker { margin-top: 18rpx; padding: 18rpx 22rpx; border-radius: 26rpx; background: rgba(255,255,255,0.72); display: flex; align-items: center; justify-content: space-between; color: #42635E; font-size: 24rpx; }
|
|
|
|
|
.college-picker text:last-child { color: #22B889; font-weight: 900; }
|
|
|
|
|
.rocket { margin-top: 22rpx; display: flex; align-items: center; gap: 16rpx; }
|
|
|
|
|
.rocket-body { font-size: 56rpx; }
|
|
|
|
|
.rocket-line { flex: 1; height: 18rpx; border-radius: 999rpx; background: #E6F2EF; overflow: hidden; }
|
|
|
|
|
.rocket-progress { height: 100%; border-radius: 999rpx; background: linear-gradient(90deg, #35D6A6, #4FB7FF, #FFB84D); }
|
|
|
|
|
.section-head { display: flex; justify-content: space-between; align-items: center; font-size: 24rpx; color: #6B817D; }
|
|
|
|
|
.section-head text:first-child { color: #12342F; font-size: 31rpx; font-weight: 900; }
|
|
|
|
|
.college-row { margin-top: 18rpx; display: flex; align-items: center; gap: 16rpx; }
|
|
|
|
|
.medal { width: 48rpx; height: 48rpx; line-height: 48rpx; border-radius: 50%; text-align: center; background: #FFF1C7; color: #B87932; font-weight: 900; }
|
|
|
|
|
.college-main { flex: 1; min-width: 0; }
|
|
|
|
|
.college-main text { font-size: 25rpx; font-weight: 800; }
|
|
|
|
|
.bar { margin-top: 10rpx; height: 12rpx; background: #EEF7F4; border-radius: 999rpx; overflow: hidden; }
|
|
|
|
|
.bar view { height: 100%; border-radius: 999rpx; background: linear-gradient(90deg, #35D6A6, #4FB7FF); }
|
|
|
|
|
.score { width: 92rpx; text-align: right; color: #22B889; font-size: 28rpx; font-weight: 900; }
|
|
|
|
|
.empty { margin-top: 18rpx; color: #8AA09C; font-size: 24rpx; }
|
|
|
|
|
.board { margin-top: 22rpx; height: 620rpx; border-radius: 36rpx; background: linear-gradient(155deg, rgba(234,248,255,0.9), rgba(255,248,222,0.72)); position: relative; overflow: hidden; }
|
|
|
|
|
.tile { position: absolute; width: 76rpx; height: 76rpx; border-radius: 22rpx; background: #fff; display: flex; align-items: center; justify-content: center; box-shadow: 0 10rpx 22rpx rgba(66,99,94,0.12); border: 2rpx solid rgba(255,255,255,0.9); transition: transform .12s ease, opacity .12s ease; }
|
|
|
|
|
.tile text { font-size: 42rpx; }
|
|
|
|
|
.tile.locked { opacity: .42; background: #F2F6F5; }
|
|
|
|
|
.tile.selected { opacity: 0; transform: scale(.5); pointer-events: none; }
|
|
|
|
|
.slot-wrap { margin-top: 24rpx; }
|
|
|
|
|
.slot-title { color: #6B817D; font-size: 24rpx; margin-bottom: 14rpx; }
|
|
|
|
|
.slots { display: flex; gap: 10rpx; }
|
|
|
|
|
.slot { flex: 1; height: 74rpx; border-radius: 20rpx; background: rgba(255,255,255,0.8); border: 2rpx dashed rgba(53,214,166,0.28); display: flex; align-items: center; justify-content: center; }
|
|
|
|
|
.slot text { font-size: 38rpx; }
|
|
|
|
|
.actions { margin-top: 28rpx; display: flex; gap: 18rpx; }
|
|
|
|
|
.sub-btn, .main-btn { flex: 1; height: 82rpx; line-height: 82rpx; text-align: center; border-radius: 999rpx; font-size: 28rpx; font-weight: 900; }
|
|
|
|
|
.sub-btn { background: rgba(255,255,255,0.78); color: #42635E; }
|
|
|
|
|
.main-btn { background: linear-gradient(135deg, #35D6A6, #4FB7FF); color: #fff; box-shadow: 0 14rpx 34rpx rgba(53,214,166,0.2); }
|
|
|
|
|
.main-btn.disabled { opacity: .45; }
|
|
|
|
|
.modal { position: fixed; inset: 0; z-index: 40; background: rgba(18,52,47,0.2); display: flex; align-items: center; justify-content: center; padding: 46rpx; }
|
|
|
|
|
.modal-card { width: 100%; border-radius: 42rpx; background: #fff; padding: 42rpx; text-align: center; }
|
|
|
|
|
.modal-title { font-size: 40rpx; font-weight: 900; }
|
|
|
|
|
.modal-sub { margin: 18rpx 0 30rpx; color: #6B817D; font-size: 26rpx; line-height: 1.55; }
|
|
|
|
|
.college-modal { text-align: left; }
|
|
|
|
|
.college-modal .modal-title, .college-modal .modal-sub { text-align: center; }
|
|
|
|
|
.college-list { max-height: 560rpx; margin-bottom: 24rpx; }
|
|
|
|
|
.college-option { margin-bottom: 14rpx; padding: 20rpx; border-radius: 26rpx; background: #F5FBFA; display: flex; align-items: center; gap: 18rpx; border: 2rpx solid transparent; }
|
|
|
|
|
.college-option.active { border-color: #35D6A6; background: #EEFFF9; }
|
|
|
|
|
.college-option > text { font-size: 42rpx; width: 56rpx; text-align: center; }
|
|
|
|
|
.college-option view { display: flex; flex-direction: column; }
|
|
|
|
|
.college-option view text:first-child { color: #12342F; font-size: 28rpx; font-weight: 900; }
|
|
|
|
|
.college-option view text:last-child { color: #6B817D; font-size: 22rpx; margin-top: 6rpx; }
|
|
|
|
|
</style>
|