Spaces:
Sleeping
Sleeping
Upload 7 files
Browse files- Dockerfile +10 -0
- app.py +74 -0
- coins.json +26 -0
- requirements.txt +4 -0
- static/script.js +83 -0
- static/styles.css +54 -0
- templates/index.html +42 -0
Dockerfile
ADDED
@@ -0,0 +1,10 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
FROM python:3.9-slim
|
2 |
+
|
3 |
+
WORKDIR /app
|
4 |
+
|
5 |
+
COPY requirements.txt .
|
6 |
+
RUN pip install --no-cache-dir -r requirements.txt
|
7 |
+
|
8 |
+
COPY . .
|
9 |
+
|
10 |
+
CMD ["python", "app.py"]
|
app.py
ADDED
@@ -0,0 +1,74 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
from flask import Flask, render_template, jsonify, request
|
2 |
+
import json
|
3 |
+
import random
|
4 |
+
from transformers import AutoTokenizer, AutoModelForCausalLM
|
5 |
+
import torch
|
6 |
+
|
7 |
+
app = Flask(__name__)
|
8 |
+
|
9 |
+
# Load initial coins
|
10 |
+
with open('coins.json', 'r') as f:
|
11 |
+
coins = json.load(f)
|
12 |
+
|
13 |
+
# Initialize game state
|
14 |
+
game_state = {
|
15 |
+
'balance': 0,
|
16 |
+
'flips_left': 1000,
|
17 |
+
'current_coin': 0
|
18 |
+
}
|
19 |
+
|
20 |
+
# Load AI model
|
21 |
+
tokenizer = AutoTokenizer.from_pretrained("TinyLlama/TinyLlama-1.1B-Chat-v1.0")
|
22 |
+
model = AutoModelForCausalLM.from_pretrained("TinyLlama/TinyLlama-1.1B-Chat-v1.0")
|
23 |
+
|
24 |
+
@app.route('/')
|
25 |
+
def index():
|
26 |
+
return render_template('index.html', coins=coins, game_state=game_state)
|
27 |
+
|
28 |
+
@app.route('/flip', methods=['POST'])
|
29 |
+
def flip_coin():
|
30 |
+
if game_state['flips_left'] > 0:
|
31 |
+
coin = coins[game_state['current_coin']]
|
32 |
+
is_heads = random.random() < coin['winrate']
|
33 |
+
if is_heads:
|
34 |
+
game_state['balance'] += coin['value']
|
35 |
+
game_state['flips_left'] -= 1
|
36 |
+
return jsonify({
|
37 |
+
'result': 'H' if is_heads else 'T',
|
38 |
+
'balance': game_state['balance'],
|
39 |
+
'flips_left': game_state['flips_left']
|
40 |
+
})
|
41 |
+
return jsonify({'error': 'No flips left'})
|
42 |
+
|
43 |
+
@app.route('/buy', methods=['POST'])
|
44 |
+
def buy_coin():
|
45 |
+
index = int(request.json['index'])
|
46 |
+
if index < len(coins) and game_state['balance'] >= coins[index]['price']:
|
47 |
+
game_state['balance'] -= coins[index]['price']
|
48 |
+
game_state['current_coin'] = index
|
49 |
+
return jsonify({'success': True, 'balance': game_state['balance']})
|
50 |
+
return jsonify({'success': False})
|
51 |
+
|
52 |
+
@app.route('/generate_coin', methods=['POST'])
|
53 |
+
def generate_coin():
|
54 |
+
prompt = "Generate a new coin for a game in JSON format with the following properties: color (as a hex code), price, value, and winrate. The price should be higher than 10, the value should be higher than 0.1, and the winrate should be between 0.5 and 0.8."
|
55 |
+
|
56 |
+
inputs = tokenizer(prompt, return_tensors="pt")
|
57 |
+
with torch.no_grad():
|
58 |
+
outputs = model.generate(**inputs, max_length=200)
|
59 |
+
response = tokenizer.decode(outputs[0], skip_special_tokens=True)
|
60 |
+
|
61 |
+
try:
|
62 |
+
new_coin = json.loads(response)
|
63 |
+
if all(key in new_coin for key in ['color', 'price', 'value', 'winrate']):
|
64 |
+
coins.append(new_coin)
|
65 |
+
return jsonify({'success': True, 'coin': new_coin})
|
66 |
+
else:
|
67 |
+
print("Error: Generated coin does not have all required properties")
|
68 |
+
return jsonify({'success': False, 'error': 'Invalid coin format'})
|
69 |
+
except json.JSONDecodeError:
|
70 |
+
print("Error: Could not parse generated coin as JSON")
|
71 |
+
return jsonify({'success': False, 'error': 'Invalid JSON format'})
|
72 |
+
|
73 |
+
if __name__ == '__main__':
|
74 |
+
app.run(host='0.0.0.0', port=7860)
|
coins.json
ADDED
@@ -0,0 +1,26 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
[
|
2 |
+
{
|
3 |
+
"color": "#CD7F32",
|
4 |
+
"price": 0.0,
|
5 |
+
"value": 0.01,
|
6 |
+
"winrate": 0.5
|
7 |
+
},
|
8 |
+
{
|
9 |
+
"color": "#C0C0C0",
|
10 |
+
"price": 1.0,
|
11 |
+
"value": 0.02,
|
12 |
+
"winrate": 0.55
|
13 |
+
},
|
14 |
+
{
|
15 |
+
"color": "#FFD700",
|
16 |
+
"price": 5.0,
|
17 |
+
"value": 0.05,
|
18 |
+
"winrate": 0.6
|
19 |
+
},
|
20 |
+
{
|
21 |
+
"color": "#E5E4E2",
|
22 |
+
"price": 10.0,
|
23 |
+
"value": 0.1,
|
24 |
+
"winrate": 0.65
|
25 |
+
}
|
26 |
+
]
|
requirements.txt
ADDED
@@ -0,0 +1,4 @@
|
|
|
|
|
|
|
|
|
|
|
1 |
+
flask
|
2 |
+
transformers
|
3 |
+
torch
|
4 |
+
uvicorn
|
static/script.js
ADDED
@@ -0,0 +1,83 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
let balance = 0;
|
2 |
+
let flipsLeft = 1000;
|
3 |
+
let currentCoin = 0;
|
4 |
+
|
5 |
+
function updateInfo() {
|
6 |
+
document.getElementById("balance").textContent = balance.toFixed(2);
|
7 |
+
document.getElementById("flips-left").textContent = flipsLeft;
|
8 |
+
}
|
9 |
+
|
10 |
+
function flipCoin() {
|
11 |
+
if (flipsLeft > 0) {
|
12 |
+
fetch("/flip", {
|
13 |
+
method: "POST",
|
14 |
+
headers: {
|
15 |
+
"Content-Type": "application/json",
|
16 |
+
},
|
17 |
+
body: JSON.stringify({ coin_index: currentCoin }),
|
18 |
+
})
|
19 |
+
.then((response) => response.json())
|
20 |
+
.then((data) => {
|
21 |
+
const coin = document.getElementById("coin");
|
22 |
+
coin.textContent = data.is_heads ? "H" : "T";
|
23 |
+
if (data.is_heads) {
|
24 |
+
balance += data.value;
|
25 |
+
}
|
26 |
+
flipsLeft--;
|
27 |
+
updateInfo();
|
28 |
+
});
|
29 |
+
}
|
30 |
+
}
|
31 |
+
|
32 |
+
function buyCoin(index) {
|
33 |
+
fetch("/buy", {
|
34 |
+
method: "POST",
|
35 |
+
headers: {
|
36 |
+
"Content-Type": "application/json",
|
37 |
+
},
|
38 |
+
body: JSON.stringify({ coin_index: index, balance: balance }),
|
39 |
+
})
|
40 |
+
.then((response) => response.json())
|
41 |
+
.then((data) => {
|
42 |
+
if (data.success) {
|
43 |
+
balance -= coins[index].price;
|
44 |
+
currentCoin = index;
|
45 |
+
document.getElementById("coin").style.backgroundColor =
|
46 |
+
coins[index].color;
|
47 |
+
updateInfo();
|
48 |
+
} else {
|
49 |
+
alert("Not enough money to buy this coin!");
|
50 |
+
}
|
51 |
+
});
|
52 |
+
}
|
53 |
+
|
54 |
+
function generateCoin() {
|
55 |
+
fetch("/generate_coin", {
|
56 |
+
method: "POST",
|
57 |
+
})
|
58 |
+
.then((response) => response.json())
|
59 |
+
.then((data) => {
|
60 |
+
if (data.success) {
|
61 |
+
coins.push(data.coin);
|
62 |
+
const shop = document.getElementById("shop");
|
63 |
+
const newCoin = document.createElement("div");
|
64 |
+
newCoin.className = "shop-coin";
|
65 |
+
newCoin.style.backgroundColor = data.coin.color;
|
66 |
+
newCoin.innerHTML = `
|
67 |
+
<div>P: ${data.coin.winrate.toFixed(2)}</div>
|
68 |
+
<div>V: $${data.coin.value.toFixed(2)}</div>
|
69 |
+
<div>C: $${data.coin.price.toFixed(2)}</div>
|
70 |
+
`;
|
71 |
+
newCoin.onclick = () => buyCoin(coins.length - 1);
|
72 |
+
shop.appendChild(newCoin);
|
73 |
+
} else {
|
74 |
+
console.error("Failed to generate new coin:", data.error);
|
75 |
+
}
|
76 |
+
});
|
77 |
+
}
|
78 |
+
|
79 |
+
document.addEventListener("DOMContentLoaded", () => {
|
80 |
+
updateInfo();
|
81 |
+
document.getElementById("coin").onclick = flipCoin;
|
82 |
+
document.getElementById("generate-coin").onclick = generateCoin;
|
83 |
+
});
|
static/styles.css
ADDED
@@ -0,0 +1,54 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
body {
|
2 |
+
font-family: Arial, sans-serif;
|
3 |
+
display: flex;
|
4 |
+
flex-direction: column;
|
5 |
+
align-items: center;
|
6 |
+
background-color: #f0f0f0;
|
7 |
+
}
|
8 |
+
|
9 |
+
#game-info {
|
10 |
+
margin-bottom: 20px;
|
11 |
+
font-size: 18px;
|
12 |
+
}
|
13 |
+
|
14 |
+
#coin {
|
15 |
+
width: 200px;
|
16 |
+
height: 200px;
|
17 |
+
border-radius: 50%;
|
18 |
+
display: flex;
|
19 |
+
justify-content: center;
|
20 |
+
align-items: center;
|
21 |
+
font-size: 72px;
|
22 |
+
cursor: pointer;
|
23 |
+
margin-bottom: 20px;
|
24 |
+
}
|
25 |
+
|
26 |
+
#shop {
|
27 |
+
display: flex;
|
28 |
+
justify-content: space-around;
|
29 |
+
width: 100%;
|
30 |
+
max-width: 800px;
|
31 |
+
margin-top: 20px;
|
32 |
+
}
|
33 |
+
|
34 |
+
.shop-item {
|
35 |
+
text-align: center;
|
36 |
+
padding: 10px;
|
37 |
+
border: 1px solid #ccc;
|
38 |
+
border-radius: 5px;
|
39 |
+
cursor: pointer;
|
40 |
+
}
|
41 |
+
|
42 |
+
.shop-coin {
|
43 |
+
width: 50px;
|
44 |
+
height: 50px;
|
45 |
+
border-radius: 50%;
|
46 |
+
margin: 0 auto;
|
47 |
+
}
|
48 |
+
|
49 |
+
#generate-coin {
|
50 |
+
margin-top: 20px;
|
51 |
+
padding: 10px 20px;
|
52 |
+
font-size: 16px;
|
53 |
+
cursor: pointer;
|
54 |
+
}
|
templates/index.html
ADDED
@@ -0,0 +1,42 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
<!doctype html>
|
2 |
+
<html lang="en">
|
3 |
+
<head>
|
4 |
+
<meta charset="UTF-8" />
|
5 |
+
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
6 |
+
<title>Pennyflip</title>
|
7 |
+
<link
|
8 |
+
rel="stylesheet"
|
9 |
+
href="{{ url_for('static', filename='styles.css') }}"
|
10 |
+
/>
|
11 |
+
</head>
|
12 |
+
<body>
|
13 |
+
<div id="game-container">
|
14 |
+
<div id="info">
|
15 |
+
<h2>Pennyflip</h2>
|
16 |
+
<p>
|
17 |
+
Balance: $<span id="balance">0.00</span> | Flips left:
|
18 |
+
<span id="flips-left">1000</span>
|
19 |
+
</p>
|
20 |
+
</div>
|
21 |
+
<div id="coin" style="background-color: {{ coins[0].color }}"></div>
|
22 |
+
<div id="shop">
|
23 |
+
{% for coin in coins %}
|
24 |
+
<div
|
25 |
+
class="shop-coin"
|
26 |
+
style="background-color: {{ coin.color }}"
|
27 |
+
onclick="buyCoin({{ loop.index0 }})"
|
28 |
+
>
|
29 |
+
<div>P: {{ "%.2f"|format(coin.winrate) }}</div>
|
30 |
+
<div>V: ${{ "%.2f"|format(coin.value) }}</div>
|
31 |
+
<div>C: ${{ "%.2f"|format(coin.price) }}</div>
|
32 |
+
</div>
|
33 |
+
{% endfor %}
|
34 |
+
</div>
|
35 |
+
<button id="generate-coin">Generate New Coin</button>
|
36 |
+
</div>
|
37 |
+
<script>
|
38 |
+
const coins = {{ coins|tojson|safe }};
|
39 |
+
</script>
|
40 |
+
<script src="{{ url_for('static', filename='script.js') }}"></script>
|
41 |
+
</body>
|
42 |
+
</html>
|