From 756f6131cbba0b8625fe85ab1938f7bc7d2dbc9f Mon Sep 17 00:00:00 2001 From: wangfukang <15630117759@163.com> Date: Tue, 2 Jun 2026 12:26:00 +0800 Subject: [PATCH] 1 --- package1/planet/adventure.vue | 256 ++++++++++++++++++++++++++++++---- 1 file changed, 227 insertions(+), 29 deletions(-) diff --git a/package1/planet/adventure.vue b/package1/planet/adventure.vue index 450c647..b5fa136 100644 --- a/package1/planet/adventure.vue +++ b/package1/planet/adventure.vue @@ -9,6 +9,10 @@ COLLEGE ROCKET 今天为 {{collegeName}} 推一把 + + {{college ? '已选择学院' : '先选择学院'}} + {{collegeName}} › + 🚀 @@ -42,6 +46,7 @@ 规则:点选未被压住的卡牌,底部槽内3张相同自动消除,槽满则失败。 + 剩余 {{remainingCount}} 张 · 当前可点 {{availableCount}} 张 · {{riskText}} @@ -81,6 +86,29 @@ 知道了 + + + + 选择你的学院 + 学院来自当前校区后台配置,可随时修改。当天通关后会为所选学院贡献推进值。 + + + {{item.icon || '🎓'}} + + {{item.collegeName}} + {{item.shortName || '学院战队'}} + + + + 当前校区还没有配置学院,请联系后台先维护学院配置 + 确定 + + @@ -94,6 +122,8 @@ nickname: '', avatar: '', college: '', + colleges: [], + collegeModal: false, home: {}, session: null, playing: false, @@ -135,6 +165,17 @@ }, 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() { @@ -143,22 +184,33 @@ this.userId = uni.getStorageSync('id') || '' this.nickname = uni.getStorageSync('nickName') || uni.getStorageSync('nickname') || '' this.avatar = uni.getStorageSync('avatarUrl') || uni.getStorageSync('avatar') || '' - this.college = uni.getStorageSync('departmentName') || uni.getStorageSync('departmentTitle') || uni.getStorageSync('college') || '' - try { - const user = uni.getStorageSync('user') - if (!this.college && user) { - const u = JSON.parse(user) - this.college = u.departmentTitle || u.departmentName || u.departmentNameStr || u.college || '' - } - } catch (e) {} 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, @@ -174,7 +226,7 @@ startLevel() { if (this.cleared || this.playing) return if (!this.college) { - this.tui.toast('缺少学院信息,暂时无法参赛') + this.openCollegePicker() return } this.tui.request('/app/planet/adventure/start', 'POST', { @@ -195,34 +247,105 @@ }) }, buildLevel() { - const icons = ['🍔', '🥤', '📚', '🏀', '🎧', '🚲', '🍜', '📦', '☕', '🧋', '🎮', '🌟'] - const pool = [] - icons.forEach(icon => { - for (let i = 0; i < 6; i++) pool.push(icon) - }) - this.shuffle(pool) + 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 = [28, 24, 20] + 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++) { - cards.push({ + 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: pool[idx], + icon: '', layer, - x: 24 + (i % 7) * 88 + (layer * 18) + ((i % 2) * 8), - y: 34 + Math.floor(i / 7) * 86 + (layer * 54), + x: 34 + col * 96 + (layer * 24) + ((row % 2) * 18) + dx, + y: 24 + row * 78 + (layer * 44) + dy, style: '', removed: false, - selected: false - }) - cards[cards.length - 1].style = `left:${cards[cards.length - 1].x}rpx;top:${cards[cards.length - 1].y}rpx;z-index:${cards[cards.length - 1].layer + 1};` + selected: false, + solutionStep: 0 + } + card.style = `left:${card.x}rpx;top:${card.y}rpx;z-index:${card.layer + 1};` + cards.push(card) idx++ } }) - this.cards = cards - this.slots = [] - this.moveCount = 0 + 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) { @@ -257,7 +380,7 @@ isLocked(card) { return this.cards.some(other => { if (other.removed || other.selected || other.layer <= card.layer) return false - return Math.abs(other.x - card.x) < 78 && Math.abs(other.y - card.y) < 70 + return this.isOverlap(card, other) }) }, finishClear() { @@ -299,15 +422,78 @@ closeModal() { this.modal.show = false }, - shuffle(arr) { + 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(Math.random() * (i + 1)) + 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() } @@ -325,6 +511,9 @@ .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; } @@ -358,4 +547,13 @@ .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; }