Skip to content

Instantly share code, notes, and snippets.

@wwtv127
Created December 30, 2025 02:13
Show Gist options
  • Select an option

  • Save wwtv127/9922c598b009490ce325a4882e0d71ad to your computer and use it in GitHub Desktop.

Select an option

Save wwtv127/9922c598b009490ce325a4882e0d71ad to your computer and use it in GitHub Desktop.
R1 Artifact: My Project
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0, maximum-scale=1.0, user-scalable=no">
<title>R1 Uno</title>
<style>
:root {
--r1-bg: #000000;
--r1-orange: #ff4e00;
--card-red: #ea3323;
--card-blue: #0045ad;
--card-green: #33a532;
--card-yellow: #fecb00;
}
* {
box-sizing: border-box;
-webkit-tap-highlight-color: transparent;
user-select: none;
}
body {
margin: 0;
padding: 0;
background-color: var(--r1-bg);
color: white;
font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, Helvetica, Arial, sans-serif;
width: 240px;
height: 292px;
overflow: hidden;
display: flex;
flex-direction: column;
}
#game-container {
width: 100%;
height: 100%;
position: relative;
display: flex;
flex-direction: column;
padding: 5px;
}
/* Opponent Area */
#opponent-info {
height: 40px;
display: flex;
justify-content: space-between;
align-items: center;
background: rgba(255,255,255,0.1);
border-radius: 8px;
padding: 0 10px;
font-size: 12px;
}
/* Play Area */
#play-area {
flex: 1;
display: flex;
justify-content: center;
align-items: center;
gap: 15px;
}
.card {
width: 50px;
height: 75px;
border-radius: 6px;
display: flex;
justify-content: center;
align-items: center;
font-weight: bold;
font-size: 20px;
border: 2px solid white;
box-shadow: 0 2px 4px rgba(0,0,0,0.5);
position: relative;
}
.card-red { background-color: var(--card-red); }
.card-blue { background-color: var(--card-blue); }
.card-green { background-color: var(--card-green); }
.card-yellow { background-color: var(--card-yellow); }
.card-wild { background-color: #111; border-color: #555; }
.deck {
background: linear-gradient(135deg, #222, #444);
border: 2px solid #666;
cursor: pointer;
}
/* Player Hand */
#player-hand-container {
height: 100px;
overflow-x: auto;
overflow-y: hidden;
white-space: nowrap;
padding: 5px 0;
scrollbar-width: none;
}
#player-hand-container::-webkit-scrollbar { display: none; }
#player-hand {
display: inline-flex;
gap: 5px;
padding: 0 5px;
}
.hand-card {
transition: transform 0.1s;
}
.hand-card:active {
transform: translateY(-10px);
}
/* UI Overlays */
#message-overlay {
position: absolute;
top: 50%;
left: 50%;
transform: translate(-50%, -50%);
background: rgba(0,0,0,0.85);
padding: 10px;
border-radius: 10px;
border: 1px solid var(--r1-orange);
text-align: center;
z-index: 100;
display: none;
width: 80%;
}
#color-picker {
position: absolute;
bottom: 110px;
left: 50%;
transform: translateX(-50%);
background: #222;
padding: 8px;
border-radius: 10px;
display: none;
grid-template-columns: 1fr 1fr;
gap: 5px;
z-index: 110;
}
.color-btn {
width: 40px;
height: 40px;
border-radius: 5px;
border: none;
}
.btn {
background: var(--r1-orange);
color: white;
border: none;
padding: 5px 10px;
border-radius: 5px;
font-size: 12px;
margin-top: 5px;
}
</style>
</head>
<body>
<div id="game-container">
<div id="opponent-info">
<span>CPU</span>
<span id="cpu-count">Cards: 7</span>
</div>
<div id="play-area">
<div id="draw-pile" class="card deck">?</div>
<div id="discard-pile"></div>
</div>
<div id="message-overlay">
<div id="msg-text">Your Turn</div>
<button class="btn" id="msg-btn">Next</button>
</div>
<div id="color-picker">
<button class="color-btn card-red" onclick="game.selectColor('red')"></button>
<button class="color-btn card-blue" onclick="game.selectColor('blue')"></button>
<button class="color-btn card-green" onclick="game.selectColor('green')"></button>
<button class="color-btn card-yellow" onclick="game.selectColor('yellow')"></button>
</div>
<div id="player-hand-container">
<div id="player-hand"></div>
</div>
</div>
<script>
const COLORS = ['red', 'blue', 'green', 'yellow'];
const VALUES = ['0', '1', '2', '3', '4', '5', '6', '7', '8', '9', 'S', 'R', '+2'];
// Offline Sound Generator using Web Audio API
class SoundEngine {
constructor() {
this.ctx = new (window.AudioContext || window.webkitAudioContext)();
}
playTone(freq, type, duration, vol = 0.1) {
const osc = this.ctx.createOscillator();
const gain = this.ctx.createGain();
osc.type = type;
osc.frequency.setValueAtTime(freq, this.ctx.currentTime);
gain.gain.setValueAtTime(vol, this.ctx.currentTime);
gain.gain.exponentialRampToValueAtTime(0.01, this.ctx.currentTime + duration);
osc.connect(gain);
gain.connect(this.ctx.destination);
osc.start();
osc.stop(this.ctx.currentTime + duration);
}
playCard() { this.playTone(440, 'sine', 0.1); }
playDraw() { this.playTone(220, 'triangle', 0.15); }
playPower() {
this.playTone(660, 'square', 0.1, 0.05);
setTimeout(() => this.playTone(880, 'square', 0.1, 0.05), 50);
}
playWin() {
[440, 554, 659, 880].forEach((f, i) => {
setTimeout(() => this.playTone(f, 'sine', 0.4, 0.1), i * 150);
});
}
playLose() {
[440, 349, 293].forEach((f, i) => {
setTimeout(() => this.playTone(f, 'sawtooth', 0.5, 0.05), i * 200);
});
}
}
class UnoGame {
constructor() {
this.deck = [];
this.playerHand = [];
this.cpuHand = [];
this.discardPile = [];
this.turn = 'player';
this.isWildPending = false;
this.sounds = new SoundEngine();
this.init();
}
init() {
this.createDeck();
this.shuffle(this.deck);
for(let i=0; i<7; i++) {
this.playerHand.push(this.deck.pop());
this.cpuHand.push(this.deck.pop());
}
let firstCard = this.deck.pop();
while(firstCard.color === 'wild') {
this.deck.unshift(firstCard);
firstCard = this.deck.pop();
}
this.discardPile.push(firstCard);
this.render();
document.getElementById('draw-pile').onclick = () => {
this.sounds.ctx.resume(); // Resume audio context on user interaction
this.drawCard('player');
};
document.getElementById('msg-btn').onclick = () => {
this.sounds.ctx.resume();
document.getElementById('message-overlay').style.display = 'none';
if(this.turn === 'cpu') this.cpuTurn();
};
}
createDeck() {
COLORS.forEach(color => {
VALUES.forEach(val => {
this.deck.push({ color, value: val });
if(val !== '0') this.deck.push({ color, value: val });
});
});
for(let i=0; i<4; i++) {
this.deck.push({ color: 'wild', value: 'W' });
this.deck.push({ color: 'wild', value: '+4' });
}
}
shuffle(array) {
for (let i = array.length - 1; i > 0; i--) {
const j = Math.floor(Math.random() * (i + 1));
[array[i], array[j]] = [array[j], array[i]];
}
}
render() {
const handEl = document.getElementById('player-hand');
handEl.innerHTML = '';
this.playerHand.forEach((card, index) => {
const div = document.createElement('div');
div.className = `card hand-card card-${card.color}`;
div.innerText = card.value;
div.onclick = () => {
this.sounds.ctx.resume();
this.playCard(index);
};
handEl.appendChild(div);
});
const discard = this.discardPile[this.discardPile.length - 1];
const discardEl = document.getElementById('discard-pile');
discardEl.innerHTML = `<div class="card card-${discard.color}">${discard.value}</div>`;
document.getElementById('cpu-count').innerText = `Cards: ${this.cpuHand.length}`;
}
showMessage(text) {
const overlay = document.getElementById('message-overlay');
document.getElementById('msg-text').innerText = text;
overlay.style.display = 'block';
if(text.includes("Win")) this.sounds.playWin();
if(text.includes("Lose") || text.includes("CPU Wins")) this.sounds.playLose();
}
drawCard(who) {
if(this.turn !== who || this.isWildPending) return;
this.sounds.playDraw();
if(this.deck.length === 0) {
const top = this.discardPile.pop();
this.deck = [...this.discardPile];
this.shuffle(this.deck);
this.discardPile = [top];
}
const card = this.deck.pop();
if(who === 'player') {
this.playerHand.push(card);
this.render();
if(!this.canPlay(card)) {
this.turn = 'cpu';
setTimeout(() => this.showMessage("No playable card. CPU Turn"), 300);
}
} else {
this.cpuHand.push(card);
this.render();
this.turn = 'player';
}
}
canPlay(card) {
const top = this.discardPile[this.discardPile.length - 1];
return card.color === 'wild' || card.color === top.color || card.value === top.value;
}
playCard(index) {
if(this.turn !== 'player' || this.isWildPending) return;
const card = this.playerHand[index];
if(this.canPlay(card)) {
this.playerHand.splice(index, 1);
this.processCard(card);
}
}
processCard(card) {
this.discardPile.push(card);
// Sound logic
if(card.color === 'wild' || card.value === '+2' || card.value === 'S' || card.value === 'R') {
this.sounds.playPower();
} else {
this.sounds.playCard();
}
this.render();
if(this.playerHand.length === 0) return this.showMessage("You Win!");
if(this.cpuHand.length === 0) return this.showMessage("CPU Wins!");
if(card.color === 'wild') {
this.isWildPending = true;
document.getElementById('color-picker').style.display = 'grid';
if(card.value === '+4') {
for(let i=0; i<4; i++) this.cpuHand.push(this.deck.pop());
}
return;
}
let skip = false;
if(card.value === 'S' || card.value === 'R') skip = true;
if(card.value === '+2') {
for(let i=0; i<2; i++) this.cpuHand.push(this.deck.pop());
skip = true;
}
if(skip) {
this.render();
this.showMessage(`Effect! Player turns again`);
} else {
this.turn = 'cpu';
this.cpuTurn();
}
}
selectColor(color) {
this.sounds.ctx.resume();
this.sounds.playCard();
const top = this.discardPile[this.discardPile.length - 1];
top.color = color;
this.isWildPending = false;
document.getElementById('color-picker').style.display = 'none';
this.turn = 'cpu';
this.render();
this.cpuTurn();
}
cpuTurn() {
setTimeout(() => {
const playableIdx = this.cpuHand.findIndex(c => this.canPlay(c));
if(playableIdx > -1) {
const card = this.cpuHand.splice(playableIdx, 1)[0];
if(card.color === 'wild') {
card.color = COLORS[Math.floor(Math.random()*4)];
if(card.value === '+4') {
for(let i=0; i<4; i++) this.playerHand.push(this.deck.pop());
}
}
this.discardPile.push(card);
if(card.color === 'wild' || card.value === '+2' || card.value === 'S' || card.value === 'R') {
this.sounds.playPower();
} else {
this.sounds.playCard();
}
this.render();
if(card.value === 'S' || card.value === 'R' || card.value === '+2') {
if(card.value === '+2') for(let i=0; i<2; i++) this.playerHand.push(this.deck.pop());
this.render();
this.showMessage("CPU used power card! CPU turn again.");
} else {
this.turn = 'player';
this.render();
}
} else {
this.drawCard('cpu');
this.showMessage("CPU draws a card.");
}
}, 1000);
}
}
const game = new UnoGame();
</script>
</body>
</html>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment