Skip to content

Commit 37c03b1

Browse files
UI improvements and Pain Shift fix
- Remove unnecessary skip power-up button (clarify it's optional) - Fix Pain Shift: now properly triggers Target Curse and skips mutation - Add Rules cheat sheet modal with key mechanics summary - Pain Shift only available in curse-check phase (Room 4+)
1 parent 16833e0 commit 37c03b1

File tree

7 files changed

+170
-9
lines changed

7 files changed

+170
-9
lines changed

src/game/logic.ts

Lines changed: 17 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -475,8 +475,17 @@ export function acceptCurse(): void {
475475
}
476476
}
477477

478-
const nextPhase = state.pendingTargetCurseRolls > 1 ? undefined : 'mutation';
479478
const remainingRolls = state.pendingTargetCurseRolls > 0 ? state.pendingTargetCurseRolls - 1 : 0;
479+
480+
let nextPhase: typeof state.phase | undefined;
481+
if (remainingRolls > 0) {
482+
nextPhase = undefined;
483+
} else if (state.painShiftActive) {
484+
nextPhase = 'compose';
485+
addLogEntry('Pain Shift: Skipping mutation');
486+
} else {
487+
nextPhase = 'mutation';
488+
}
480489

481490
updateState({
482491
curses,
@@ -487,6 +496,7 @@ export function acceptCurse(): void {
487496
currentCurse: null,
488497
pendingCurseTargets: [],
489498
pendingTargetCurseRolls: remainingRolls,
499+
painShiftActive: remainingRolls > 0 ? state.painShiftActive : false,
490500
...(nextPhase ? { phase: nextPhase } : {}),
491501
});
492502

@@ -714,6 +724,7 @@ export function nextRoom(): void {
714724
curseTargetMethod: null,
715725
curseTargetRoll: null,
716726
pendingTargetCurseRolls: 0,
727+
painShiftActive: false,
717728
});
718729
}
719730

@@ -740,10 +751,11 @@ export function usePowerUp(type: string): void {
740751
}
741752
break;
742753
case 'painshift':
743-
addLogEntry('Power-Up: Pain Shift - No mutation, guaranteed curse');
744-
updates.currentMutation = { roll: 0, effect: 'No Mutation (Pain Shift)' };
745-
updates.phase = 'mutation-result';
746-
break;
754+
addLogEntry('Power-Up: Pain Shift - No mutation, guaranteed Target Curse');
755+
updates.painShiftActive = true;
756+
updateState(updates);
757+
rollTargetCurse();
758+
return;
747759
case 'split':
748760
addLogEntry('Power-Up: Split the Wound - Curse applied at half strength');
749761
break;

src/game/state.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -58,6 +58,7 @@ export function createInitialState(mode: GameMode, manualTrackType: boolean): Ga
5858
curseTargetMethod: null,
5959
curseTargetRoll: null,
6060
pendingTargetCurseRolls: 0,
61+
painShiftActive: false,
6162
};
6263
}
6364

src/game/types.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -81,6 +81,7 @@ export interface GameState {
8181
curseTargetMethod: CurseTargetMethod | null;
8282
curseTargetRoll: number | null;
8383
pendingTargetCurseRolls: number;
84+
painShiftActive: boolean;
8485
}
8586

8687
export type PowerUpType = 'redirect' | 'lock' | 'painshift' | 'split' | 'breath';

src/index.html

Lines changed: 66 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -55,6 +55,7 @@ <h2>Continue Run</h2>
5555
<span id="timer" class="badge timer hidden">5:00</span>
5656
</div>
5757
<div class="flex">
58+
<button id="cheatsheet-btn" class="small secondary">Rules</button>
5859
<button id="export-btn" class="small secondary">Export</button>
5960
<button id="end-run" class="small danger">End Run</button>
6061
</div>
@@ -70,14 +71,14 @@ <h2>Room <span id="current-room">1</span></h2>
7071
<!-- Power-Ups Panel -->
7172
<div id="powerup-panel" class="card hidden">
7273
<h2>Use Power-Up</h2>
74+
<p class="muted" style="margin-bottom: 0.5rem;">Optional - game continues without using one</p>
7375
<div class="grid grid-2">
7476
<button class="secondary powerup-btn" data-type="redirect">Curse Redirect</button>
7577
<button class="secondary powerup-btn" data-type="lock">Room Lock</button>
7678
<button class="secondary powerup-btn" data-type="painshift">Pain Shift</button>
7779
<button class="secondary powerup-btn" data-type="split">Split the Wound</button>
7880
<button class="secondary powerup-btn" data-type="breath">One Last Breath</button>
7981
</div>
80-
<button id="skip-powerup" class="secondary" style="margin-top: 0.5rem; width: 100%;">Skip</button>
8182
</div>
8283

8384
<!-- Tracks List -->
@@ -109,6 +110,70 @@ <h3 style="margin-top: 1rem;">Tags</h3>
109110
</div>
110111
</div>
111112

113+
<!-- Cheat Sheet Modal -->
114+
<div id="cheatsheet-modal" class="modal hidden">
115+
<div class="modal-content">
116+
<div class="modal-header">
117+
<h2>Super Beatmaker Rules</h2>
118+
<button id="close-cheatsheet" class="small secondary">Close</button>
119+
</div>
120+
<div class="modal-body">
121+
<h3>System Flow</h3>
122+
<ol>
123+
<li>Declare Track Type</li>
124+
<li>Curse Check (01-70: No Curse, 71-98: Target Curse, 99-100: Mix Curse)</li>
125+
<li>Roll Mutation</li>
126+
<li>Compose Track</li>
127+
<li>Room Finalizes</li>
128+
<li>Roll for Power-Up (if did not use)</li>
129+
</ol>
130+
131+
<h3>Power-Ups</h3>
132+
<ul>
133+
<li><strong>Curse Redirect:</strong> Reroll what Track receives a Curse</li>
134+
<li><strong>Room Lock:</strong> (1/Run) Prevent a Track from future Target Curses</li>
135+
<li><strong>Pain Shift:</strong> (Room 4+) No Mutation, guaranteed Target Curse</li>
136+
<li><strong>Split the Wound:</strong> Apply curse to 2 Tracks at half strength</li>
137+
<li><strong>One Last Breath:</strong> (1/Run) Reverse one Curse, force final Room</li>
138+
</ul>
139+
140+
<h3>Earning Power-Ups</h3>
141+
<p>After Room without using Power-Up: 01-75: None, 76-97: +1, 98-100: +2</p>
142+
143+
<h3>Curse Target (2 rolls)</h3>
144+
<p><strong>First:</strong> 01-20: Previous, 21-40: Oldest, 41-60: Loudest, 61-80: Quietest, 81-95: Choice, 96-100: Two Targets</p>
145+
<p><strong>Second:</strong> 01-32: Track Before, 33-66: That Track, 67-100: Track After</p>
146+
147+
<h3>Key Mutations</h3>
148+
<ul>
149+
<li>63-64: Roll twice, apply both</li>
150+
<li>71-72: Abandon Track Type</li>
151+
<li>85-86: 5-minute timer</li>
152+
<li>93-94: Take Target Curse instead</li>
153+
<li>97-98: Roll 80+ = delete Track</li>
154+
</ul>
155+
156+
<h3>Key Target Curses</h3>
157+
<ul>
158+
<li>33-36: Force Room + Double Mutation</li>
159+
<li>45-48: Track becomes permanent curse target</li>
160+
<li>57-60: Delete Track</li>
161+
<li>93-96: Apply last Curse to another Track</li>
162+
<li>97-100: Re-roll twice</li>
163+
</ul>
164+
165+
<h3>Key Mix Curses</h3>
166+
<ul>
167+
<li>01: This is the last Room</li>
168+
<li>92-100: Roll three Target Curses</li>
169+
</ul>
170+
171+
<h3>End Condition</h3>
172+
<p>Run ends when you declare it over with no forced Rooms. If rules followed, you win!</p>
173+
</div>
174+
</div>
175+
</div>
176+
112177
<script type="module" src="./main.js"></script>
113178
</body>
114179
</html>

src/main.ts

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -69,12 +69,30 @@ function newRun(): void {
6969
showScreen('setup');
7070
}
7171

72+
function toggleCheatSheet(show: boolean): void {
73+
const modal = $('cheatsheet-modal');
74+
if (modal) {
75+
modal.classList.toggle('hidden', !show);
76+
}
77+
}
78+
7279
function init(): void {
7380
onClick('start-run', startRun);
7481
onClick('continue-run', continueRun);
7582
onClick('import-btn', handleImport);
7683
onClick('end-run', endRun);
7784
onClick('new-run', newRun);
85+
onClick('cheatsheet-btn', () => toggleCheatSheet(true));
86+
onClick('close-cheatsheet', () => toggleCheatSheet(false));
87+
88+
const cheatsheetModal = $('cheatsheet-modal');
89+
if (cheatsheetModal) {
90+
cheatsheetModal.addEventListener('click', (e) => {
91+
if (e.target === cheatsheetModal) {
92+
toggleCheatSheet(false);
93+
}
94+
});
95+
}
7896

7997
const importInput = $('import-input');
8098
if (importInput) {

src/styles.css

Lines changed: 66 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -297,6 +297,72 @@ select {
297297
display: none;
298298
}
299299

300+
.modal {
301+
position: fixed;
302+
top: 0;
303+
left: 0;
304+
right: 0;
305+
bottom: 0;
306+
background: rgba(0, 0, 0, 0.8);
307+
display: flex;
308+
align-items: center;
309+
justify-content: center;
310+
z-index: 1000;
311+
padding: 1rem;
312+
}
313+
314+
.modal-content {
315+
background: var(--surface);
316+
border: 1px solid var(--border);
317+
border-radius: 8px;
318+
max-width: 600px;
319+
max-height: 80vh;
320+
width: 100%;
321+
display: flex;
322+
flex-direction: column;
323+
}
324+
325+
.modal-header {
326+
display: flex;
327+
justify-content: space-between;
328+
align-items: center;
329+
padding: 1rem;
330+
border-bottom: 1px solid var(--border);
331+
}
332+
333+
.modal-header h2 {
334+
margin: 0;
335+
}
336+
337+
.modal-body {
338+
padding: 1rem;
339+
overflow-y: auto;
340+
font-size: 0.9rem;
341+
}
342+
343+
.modal-body h3 {
344+
color: var(--accent);
345+
margin-top: 1rem;
346+
margin-bottom: 0.5rem;
347+
}
348+
349+
.modal-body h3:first-child {
350+
margin-top: 0;
351+
}
352+
353+
.modal-body ul, .modal-body ol {
354+
margin-left: 1.5rem;
355+
margin-bottom: 0.5rem;
356+
}
357+
358+
.modal-body li {
359+
margin-bottom: 0.25rem;
360+
}
361+
362+
.modal-body p {
363+
margin-bottom: 0.5rem;
364+
}
365+
300366
@media (max-width: 600px) {
301367
.container {
302368
padding: 0.5rem;

src/ui/render.ts

Lines changed: 1 addition & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -106,7 +106,7 @@ function renderPowerUpPanel(): void {
106106
let disabled = false;
107107

108108
if (type === 'lock' && state.usedRoomLock) disabled = true;
109-
if (type === 'painshift' && state.room <= 3) disabled = true;
109+
if (type === 'painshift' && (state.room <= 3 || state.phase !== 'curse-check')) disabled = true;
110110
if (type === 'breath' && state.usedOneLastBreath) disabled = true;
111111
if (type === 'redirect' && state.phase !== 'curse-result') disabled = true;
112112
if (type === 'split' && state.phase !== 'curse-result') disabled = true;
@@ -447,8 +447,6 @@ export function setupPowerUpButtons(): void {
447447
}
448448
};
449449
});
450-
451-
onClick('skip-powerup', () => renderPowerUpPanel());
452450
}
453451

454452
export function setupExportButton(): void {

0 commit comments

Comments
 (0)