Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Binary file added __pycache__/app.cpython-314.pyc
Binary file not shown.
160 changes: 160 additions & 0 deletions app.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,160 @@
#!/usr/bin/env python
# encoding: utf-8
"""
2048 Game with Flask GUI
"""
import random
from flask import Flask, render_template, jsonify, request, session

app = Flask(__name__)
app.secret_key = '2048_game_secret_key'

def init():
matrix = [0 for i in range(16)]
random_lst = random.sample(range(16), 2)
matrix[random_lst[0]] = matrix[random_lst[1]] = 2
return matrix

def move(matrix, direction):
mergedList = []
score = 0
if direction == 'w':
for i in range(16):
j = i
while j - 4 >= 0:
if matrix[j-4] == 0:
matrix[j-4] = matrix[j]
matrix[j] = 0
elif matrix[j-4] == matrix[j] and j not in mergedList and j-4 not in mergedList:
matrix[j-4] *= 2
score += matrix[j-4]
matrix[j] = 0
mergedList.append(j-4)
mergedList.append(j)
j -= 4
elif direction == 's':
for i in range(15, -1, -1):
j = i
while j + 4 < 16:
if matrix[j+4] == 0:
matrix[j+4] = matrix[j]
matrix[j] = 0
elif matrix[j+4] == matrix[j] and j not in mergedList and j+4 not in mergedList:
matrix[j+4] *= 2
score += matrix[j+4]
matrix[j] = 0
mergedList.append(j)
mergedList.append(j+4)
j += 4
elif direction == 'a':
for i in range(16):
j = i
while j % 4 != 0:
if matrix[j-1] == 0:
matrix[j-1] = matrix[j]
matrix[j] = 0
elif matrix[j-1] == matrix[j] and j not in mergedList and j-1 not in mergedList:
matrix[j-1] *= 2
score += matrix[j-1]
matrix[j] = 0
mergedList.append(j-1)
mergedList.append(j)
j -= 1
else:
for i in range(15, -1, -1):
j = i
while j % 4 != 3:
if matrix[j+1] == 0:
matrix[j+1] = matrix[j]
matrix[j] = 0
elif matrix[j+1] == matrix[j] and j not in mergedList and j+1 not in mergedList:
matrix[j+1] *= 2
score += matrix[j+1]
matrix[j] = 0
mergedList.append(j)
mergedList.append(j+1)
j += 1
return matrix, score

def insert(matrix):
getZeroIndex = []
for i in range(16):
if matrix[i] == 0:
getZeroIndex.append(i)
if not getZeroIndex:
return matrix

randomZeroIndex = random.choice(getZeroIndex)
max_num = max(matrix)

if max_num > 128:
candidates = [2, 4, 8, 16, 32]
weights = [60, 25, 10, 4, 1]
else:
candidates = [2, 4, 8]
weights = [70, 25, 5]

total = sum(weights)
r = random.randint(1, total)
for num, w in zip(candidates, weights):
if r <= w:
matrix[randomZeroIndex] = num
break
r -= w

return matrix

def isOver(matrix):
if 0 in matrix:
return False
else:
for i in range(16):
if i % 4 != 3:
if matrix[i] == matrix[i+1]:
return False
if i < 12:
if matrix[i] == matrix[i+4]:
return False
return True

def has_2048(matrix):
return 2048 in matrix

def get_game_state():
if 'matrix' not in session:
session['matrix'] = init()
session['score'] = 0
return {
'matrix': session['matrix'],
'score': session['score'],
'game_over': isOver(session['matrix']),
'win': has_2048(session['matrix'])
}

@app.route('/')
def index():
return render_template('index.html')

@app.route('/new_game', methods=['POST'])
def new_game():
session['matrix'] = init()
session['score'] = 0
return jsonify(get_game_state())

@app.route('/move', methods=['POST'])
def make_move():
data = request.get_json()
direction = data.get('direction')

old_matrix = list(session['matrix'])
matrix, add_score = move(session['matrix'], direction)

if matrix != old_matrix:
insert(matrix)
session['score'] += add_score

session['matrix'] = matrix
return jsonify(get_game_state())

if __name__ == '__main__':
app.run(debug=True)
238 changes: 238 additions & 0 deletions templates/index.html
Original file line number Diff line number Diff line change
@@ -0,0 +1,238 @@
<!DOCTYPE html>
<html lang="zh-CN">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>2048 Game</title>
<style>
body {
font-family: 'Arial', sans-serif;
display: flex;
flex-direction: column;
align-items: center;
background-color: #faf8ef;
margin: 0;
padding: 20px;
}
h1 {
color: #776e65;
font-size: 80px;
margin: 10px 0;
}
.score-container {
background: #bbada0;
padding: 10px 20px;
border-radius: 5px;
color: white;
font-size: 25px;
font-weight: bold;
margin-bottom: 20px;
}
.game-container {
width: 460px;
height: 460px;
background-color: #bbada0;
border-radius: 6px;
padding: 15px;
position: relative;
box-sizing: border-box;
}
.grid-cell {
width: 100px;
height: 100px;
margin: 7.5px;
float: left;
border-radius: 3px;
background: rgba(238, 228, 218, 0.35);
}
.tile {
position: absolute;
width: 100px;
height: 100px;
border-radius: 3px;
font-size: 55px;
font-weight: bold;
display: flex;
align-items: center;
justify-content: center;
transition: left 0.1s ease-in-out, top 0.1s ease-in-out;
}
.tile-2 { background: #eee4da; color: #776e65; }
.tile-4 { background: #ede0c8; color: #776e65; }
.tile-8 { background: #f2b179; color: #f9f6f2; }
.tile-16 { background: #f59563; color: #f9f6f2; }
.tile-32 { background: #f67c5f; color: #f9f6f2; }
.tile-64 { background: #f65e3b; color: #f9f6f2; }
.tile-128 { background: #edcf72; color: #f9f6f2; font-size: 45px; }
.tile-256 { background: #edcc61; color: #f9f6f2; font-size: 45px; }
.tile-512 { background: #edc850; color: #f9f6f2; font-size: 45px; }
.tile-1024 { background: #edc53f; color: #f9f6f2; font-size: 35px; }
.tile-2048 { background: #edc22e; color: #f9f6f2; font-size: 35px; }
.tile-super { background: #3c3a32; color: #f9f6f2; font-size: 30px; }
.message {
position: absolute;
top: 0;
left: 0;
right: 0;
bottom: 0;
background: rgba(238, 228, 218, 0.73);
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
font-size: 60px;
font-weight: bold;
color: #776e65;
z-index: 100;
}
.message button {
margin-top: 20px;
padding: 10px 20px;
font-size: 25px;
background: #8f7a66;
color: white;
border: none;
border-radius: 5px;
cursor: pointer;
}
.instructions {
margin-top: 20px;
color: #776e65;
font-size: 18px;
text-align: center;
}
</style>
</head>
<body>
<h1>2048</h1>
<div class="score-container">
分数: <span id="score">0</span>
</div>
<div class="game-container">
{% for i in range(4) %}
{% for j in range(4) %}
<div class="grid-cell"></div>
{% endfor %}
{% endfor %}
<div id="tiles"></div>
<div id="message" class="message" style="display: none;"></div>
</div>
<div class="instructions">
使用方向键移动方块<br>
合并相同数字获得更高分数!
</div>

<script>
let gameState = null;
let score = 0;

function updateTiles(matrix) {
const tilesContainer = document.getElementById('tiles');
tilesContainer.innerHTML = '';

for (let i = 0; i < 16; i++) {
const value = matrix[i];
if (value > 0) {
const row = Math.floor(i / 4);
const col = i % 4;
const tile = document.createElement('div');
tile.className = `tile tile-${value > 2048 ? 'super' : value}`;
tile.textContent = value;
tile.style.left = `${7.5 + col * 115}px`;
tile.style.top = `${7.5 + row * 115}px`;
tilesContainer.appendChild(tile);
}
}
}

function updateScore(newScore) {
score = newScore;
document.getElementById('score').textContent = score;
}

function showMessage(text, showButton = true) {
const message = document.getElementById('message');
message.innerHTML = `<div>${text}</div>`;
if (showButton) {
const button = document.createElement('button');
button.textContent = '再来一局';
button.onclick = newGame;
message.appendChild(button);
}
message.style.display = 'flex';
}

function hideMessage() {
document.getElementById('message').style.display = 'none';
}

function newGame() {
hideMessage();
fetch('/new_game', { method: 'POST' })
.then(response => response.json())
.then(data => {
gameState = data;
updateTiles(data.matrix);
updateScore(data.score);
});
}

function move(direction) {
if (!gameState || gameState.game_over) return;

fetch('/move', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ direction: direction })
})
.then(response => response.json())
.then(data => {
gameState = data;
updateTiles(data.matrix);
updateScore(data.score);

if (data.win) {
showMessage('你赢了!', true);
} else if (data.game_over) {
showMessage('游戏结束!', true);
}
});
}

document.addEventListener('keydown', function(e) {
switch(e.key) {
case 'ArrowUp':
case 'w':
case 'W':
e.preventDefault();
move('w');
break;
case 'ArrowDown':
case 's':
case 'S':
e.preventDefault();
move('s');
break;
case 'ArrowLeft':
case 'a':
case 'A':
e.preventDefault();
move('a');
break;
case 'ArrowRight':
case 'd':
case 'D':
e.preventDefault();
move('d');
break;
case 'n':
case 'N':
newGame();
break;
}
});

window.onload = newGame;
</script>
</body>
</html>