tambahkan fungsi

fungsi rating age
This commit is contained in:
Zeakeers 2025-02-26 18:32:33 +07:00
parent 0c16c70a9e
commit 003ebd1de1
14 changed files with 17888 additions and 0 deletions

147
backend/app.py Normal file
View File

@ -0,0 +1,147 @@
from flask import Flask, request, jsonify
from flask_cors import CORS
from sklearn.neighbors import KNeighborsClassifier
import pandas as pd
import os
app = Flask(__name__)
CORS(app)
DATASET_FILE = os.path.join(os.path.dirname(__file__), "dataset.xlsx")
# Mapping angka ke kategori jawaban
ANSWER_MAPPING = {
0: "Tidak Mengandung",
1: "Sangat Sedikit Mengandung",
2: "Agak Mengandung",
3: "Cukup Mengandung",
4: "Banyak Mengandung",
5: "Sangat Banyak Mengandung"
}
REVERSE_MAPPING = {v: k for k, v in ANSWER_MAPPING.items()}
# Fungsi untuk membaca dataset
def load_dataset():
if os.path.exists(DATASET_FILE):
try:
data = pd.read_excel(DATASET_FILE)
if data.empty:
data = pd.DataFrame(columns=["Nama Game", "Kekerasan", "Bahasa Kasar", "Tema Seksual", "Darah/Gore", "Elemen Perjudian", "Rating"])
data.to_excel(DATASET_FILE, index=False)
except Exception as e:
print("Error saat membaca dataset:", e)
data = pd.DataFrame(columns=["Nama Game", "Kekerasan", "Bahasa Kasar", "Tema Seksual", "Darah/Gore", "Elemen Perjudian", "Rating"])
data.to_excel(DATASET_FILE, index=False)
else:
data = pd.DataFrame(columns=["Nama Game", "Kekerasan", "Bahasa Kasar", "Tema Seksual", "Darah/Gore", "Elemen Perjudian", "Rating"])
data.to_excel(DATASET_FILE, index=False)
print("Kolom dalam dataset:", data.columns.tolist())
required_columns = ["Nama Game", "Kekerasan", "Bahasa Kasar", "Tema Seksual", "Darah/Gore", "Elemen Perjudian", "Rating"]
for col in required_columns:
if col not in data.columns:
raise ValueError(f"Kolom '{col}' tidak ditemukan dalam dataset!")
X = data.iloc[:, 1:-1].values
y = data.iloc[:, -1].values
return X, y, data
# Fungsi untuk menambahkan data ke dataset
def add_to_dataset(name, features, rating):
data = pd.read_excel(DATASET_FILE)
if name not in data['Nama Game'].values:
new_row = [name] + features + [rating]
data.loc[len(data)] = new_row
data.to_excel(DATASET_FILE, index=False)
# Endpoint untuk mengecek apakah game sudah ada dalam dataset
@app.route('/check_game', methods=['POST'])
def check_game():
try:
data = request.json
game_name = data['game_name']
_, _, dataset = load_dataset()
if game_name in dataset["Nama Game"].values:
rating = dataset.loc[dataset["Nama Game"] == game_name, "Rating"].values[0]
return jsonify({'exists': True, 'rating': rating})
return jsonify({'exists': False})
except Exception as e:
return jsonify({"error": str(e)}), 500
# Endpoint untuk menerima jawaban dan memberikan rating
@app.route('/submit_answers', methods=['POST'])
def submit_answers():
try:
data = request.json
print("=== DATA DITERIMA DARI FRONTEND ===")
print(data)
game_name = data.get('game_name', '').strip()
answers = data.get('answers', [])
if not game_name or not answers:
print("❌ ERROR: Data tidak lengkap")
return jsonify({"error": "Data tidak lengkap"}), 400
print("✔ Nama Game:", game_name)
print("✔ Jawaban:", answers)
# Cek apakah jawaban valid
features = []
for answer in answers:
if answer not in REVERSE_MAPPING:
print(f"❌ ERROR: Jawaban tidak valid -> {answer}")
return jsonify({"error": f"Jawaban tidak valid: {answer}"}), 400
features.append(REVERSE_MAPPING[answer])
print("✔ Features (dikonversi ke angka):", features)
X, y, dataset = load_dataset()
# Jika game sudah ada di dataset, kembalikan rating yang sudah ada
if game_name in dataset["Nama Game"].values:
existing_rating = dataset.loc[dataset["Nama Game"] == game_name, "Rating"].values[0]
print("✔ Game ditemukan dalam dataset. Rating:", existing_rating)
return jsonify({'rating': existing_rating})
# Prediksi rating dengan KNN jika dataset cukup besar
if len(X) > 3:
knn = KNeighborsClassifier(n_neighbors=3)
knn.fit(X, y)
predicted_rating = knn.predict([features])[0]
else:
total_score = sum(features)
if total_score <= 5:
predicted_rating = "SU"
elif total_score <= 10:
predicted_rating = "3+"
elif total_score <= 15:
predicted_rating = "7+"
elif total_score <= 20:
predicted_rating = "13+"
else:
predicted_rating = "18+"
print("✔ Prediksi Rating:", predicted_rating)
add_to_dataset(game_name, features, predicted_rating)
return jsonify({'rating': predicted_rating})
except Exception as e:
print("❌ ERROR di backend:", str(e))
return jsonify({"error": str(e)}), 500
if __name__ == '__main__':
app.run(debug=True)

BIN
backend/dataset.xlsx Normal file

Binary file not shown.

17354
frontend/package-lock.json generated Normal file

File diff suppressed because it is too large Load Diff

35
frontend/package.json Normal file
View File

@ -0,0 +1,35 @@
{
"name": "frontend",
"version": "1.0.0",
"main": "script.js",
"scripts": {
"start": "react-scripts start",
"build": "react-scripts build",
"test": "react-scripts test",
"eject": "react-scripts eject"
},
"keywords": [],
"author": "",
"license": "ISC",
"description": "",
"dependencies": {
"react-dom": "^19.0.0",
"react-scripts": "^5.0.1"
},
"browserslist": {
"production": [
">0.2%",
"not dead",
"not op_mini all"
],
"development": [
"last 1 chrome version",
"last 1 firefox version",
"last 1 safari version"
]
},
"devDependencies": {
"autoprefixer": "^10.4.20",
"postcss": "^8.5.2"
}
}

View File

@ -0,0 +1,6 @@
module.exports = {
plugins: {
tailwindcss: {},
autoprefixer: {},
},
}

View File

@ -0,0 +1,11 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>Rating Umur Game</title>
</head>
<body>
<div id="root"></div>
</body>
</html>

38
frontend/script.js Normal file
View File

@ -0,0 +1,38 @@
document.getElementById("game-form").addEventListener("submit", async (e) => {
e.preventDefault();
const formData = new FormData(e.target);
const name = formData.get("name");
const features = [
parseInt(formData.get("kekerasan")),
parseInt(formData.get("bahasa_kasar")),
parseInt(formData.get("tema_seksual")),
parseInt(formData.get("darah_gore")),
parseInt(formData.get("elemen_perjudian")),
];
try {
// Kirim data ke backend
const response = await fetch("http://127.0.0.1:5000/predict", {
method: "POST",
headers: {
"Content-Type": "application/json",
},
body: JSON.stringify({ name, features }),
});
const result = await response.json();
if (result.status === "exists") {
document.getElementById("result").textContent =
`Game "${name}" sudah ada. Rating: ${result.predicted_rating}`;
} else {
document.getElementById("result").textContent =
`Game "${name}" berhasil diprediksi. Rating: ${result.predicted_rating}`;
}
} catch (error) {
console.error("Error:", error);
alert("Gagal memproses data.");
}
});

230
frontend/src/App.js Normal file
View File

@ -0,0 +1,230 @@
import React, { useState } from "react";
import "./index.css";
import PopUp from "./components/PopUp";
const questions = [
"Seberapa banyak game ini mengandung kekerasan?",
"Seberapa sering bahasa kasar digunakan dalam game ini?",
"Seberapa banyak game ini mengandung konten dewasa?",
"Seberapa sering game ini menampilkan penggunaan obat-obatan atau alkohol?",
"Apakah game ini memiliki unsur simulasi perjudian?",
];
const options = [
"Tidak Mengandung",
"Sangat Sedikit Mengandung",
"Agak Mengandung",
"Cukup Mengandung",
"Banyak Mengandung",
"Sangat Banyak Mengandung"
];
const ratingDescriptions = {
"SU": "Semua Umur - Dapat dimainkan oleh semua orang tanpa batasan konten.",
"3+": "Usia 3 Tahun Keatas - Tidak ada konten dewasa, perjudian, atau interaksi online.",
"7+": "Usia 7 Tahun Keatas - Ada unsur ringan seperti kekerasan kartun atau bahasa ringan.",
"13+": "Usia 13 Tahun Keatas - Ada kekerasan moderat, simulasi perjudian, atau tema horor.",
"18+": "Usia 18 Tahun Keatas - Mengandung konten dewasa, kekerasan realistis, atau humor kasar."
};
const App = () => {
const [gameName, setGameName] = useState("");
const [step, setStep] = useState(0);
const [answers, setAnswers] = useState([]);
const [rating, setRating] = useState(null);
const [isPopUpOpen, setIsPopUpOpen] = useState(false);
const [isConfirmPopupOpen, setIsConfirmPopupOpen] = useState(false);
const [selectedOption, setSelectedOption] = useState("");
const checkGame = async () => {
const response = await fetch("http://127.0.0.1:5000/check_game", {
method: "POST",
headers: { "Content-Type": "application/json" },
body: JSON.stringify({ game_name: gameName }),
});
const data = await response.json();
if (data.exists) {
setRating(data.rating);
} else {
setStep(1);
}
};
const handleOptionClick = (option) => {
setSelectedOption(option);
setIsPopUpOpen(true);
};
const confirmSelection = () => {
setAnswers([...answers, selectedOption]);
setStep(step + 1);
setIsPopUpOpen(false);
};
const submitAnswers = async () => {
setIsConfirmPopupOpen(true);
};
const confirmSubmit = async () => {
setIsConfirmPopupOpen(false);
const response = await fetch("http://localhost:5000/submit_answers", {
method: "POST",
headers: { "Content-Type": "application/json" },
body: JSON.stringify({ game_name: gameName, answers }),
});
const data = await response.json();
setRating(data.rating);
};
return (
<div className="flex items-center justify-center min-h-screen bg-gray-100 relative">
<div className="w-full max-w-lg bg-white p-6 rounded-lg shadow-md text-center relative">
<h1 className="text-2xl font-bold">Rating Umur Game</h1>
{/* Tombol kembali hanya muncul saat berada di pertanyaan */}
{step > 0 && step <= questions.length && (
<div className="absolute top-4 right-4 flex space-x-2">
{step > 1 && (
<button
onClick={() => {
setAnswers(answers.slice(0, -1));
setStep(step - 1);
}}
className="w-6 h-6 bg-yellow-500 rounded-full hover:bg-yellow-700"
/>
)}
{/* Tombol merah: Kembali ke input nama game */}
<button
onClick={() => {
setStep(0);
setAnswers([]);
setGameName("");
}}
className="w-6 h-6 bg-red-500 rounded-full hover:bg-red-700"
/>
</div>
)}
{!rating && step === 0 && (
<>
<p className="mt-4">Masukkan nama game:</p>
<input
type="text"
value={gameName}
onChange={(e) => setGameName(e.target.value)}
className="border border-gray-600 bg-gray-100 p-2 w-full mt-2 focus:bg-gray-200 focus:border-black rounded mt-6"
/>
<button
onClick={checkGame}
className="bg-blue-500 text-white px-4 py-2 rounded mt-4"
>
Cek
</button>
</>
)}
{!rating && step > 0 && step <= questions.length && (
<>
<p className="mt-4">{questions[step - 1]}</p>
{options.map((option) => (
<button
key={option}
onClick={() => handleOptionClick(option)}
className="block bg-gray-300 text-black px-4 py-2 rounded mt-2 hover:bg-gray-400 w-full"
>
{option}
</button>
))}
</>
)}
{!rating && step > questions.length && (
<>
{/* Menampilkan tabel hasil jawaban */}
<div className="mt-4">
<h2 className="text-lg font-semibold">Hasil Jawaban Anda:</h2>
<table className="table-auto w-full border mt-4">
<thead>
<tr className="bg-gray-200">
<th className="border px-4 py-2">Pertanyaan</th>
<th className="border px-4 py-2">Jawaban</th>
</tr>
</thead>
<tbody>
{questions.map((question, index) => (
<tr key={index}>
<td className="border px-4 py-2">{question}</td>
<td className="border px-4 py-2">{answers[index]}</td>
</tr>
))}
</tbody>
</table>
</div>
{/* Tombol kembali ke input game */}
<button
onClick={() => {
setStep(0);
setAnswers([]);
setGameName("");
}}
className="bg-red-500 text-white px-4 py-2 rounded mt-4 w-full"
>
Kembali
</button>
{/* Tombol untuk konfirmasi hasil */}
<button
onClick={submitAnswers}
className="bg-green-500 text-white px-4 py-2 rounded mt-4 w-full"
>
Cek Hasil
</button>
</>
)}
{rating && (
<>
<h2 className="text-xl font-semibold mt-4">
Game <span className="font-bold text-blue-500">{gameName}</span> memiliki rating
<span className="font-bold text-red-500"> {rating}</span>
</h2>
<p className="mt-2"><strong>Penjelasan:</strong> {ratingDescriptions[rating]}</p>
<button
onClick={() => {
setStep(0);
setAnswers([]);
setRating(null);
setGameName("");
}}
className="bg-red-500 text-white px-4 py-2 rounded mt-4 w-full"
>
Coba Lagi
</button>
</>
)}
{/* Pop-up konfirmasi pilihan jawaban */}
<PopUp
isOpen={isPopUpOpen}
onClose={() => setIsPopUpOpen(false)}
onConfirm={confirmSelection}
selectedOption={selectedOption}
/>
{/* Pop-up konfirmasi cek hasil */}
<PopUp
isOpen={isConfirmPopupOpen}
onClose={() => setIsConfirmPopupOpen(false)}
onConfirm={confirmSubmit}
selectedOption="Apakah Anda yakin ingin melihat hasil rating?"
/>
</div>
</div>
);
};
export default App;

View File

@ -0,0 +1,30 @@
import React from "react";
const PopUp = ({ isOpen, onClose, onConfirm, selectedOption }) => {
if (!isOpen) return null;
return (
<div className="fixed inset-0 flex items-center justify-center bg-black bg-opacity-50">
<div className="bg-white p-6 rounded-lg shadow-lg text-center">
<p>Apakah Anda yakin memilih: <strong>{selectedOption}</strong>?</p>
<div className="mt-4 flex justify-center gap-4">
<button
onClick={onClose}
className="bg-gray-500 text-white px-4 py-2 rounded hover:bg-gray-700"
>
Kembali
</button>
<button
onClick={onConfirm}
className="bg-blue-500 text-white px-4 py-2 rounded hover:bg-blue-700"
>
Lanjut
</button>
</div>
</div>
</div>
);
};
export default PopUp;

3
frontend/src/index.css Normal file
View File

@ -0,0 +1,3 @@
@tailwind base;
@tailwind components;
@tailwind utilities;

15
frontend/src/index.js Normal file
View File

@ -0,0 +1,15 @@
import React from 'react';
import ReactDOM from 'react-dom/client';
import './index.css';
import App from './App';
import reportWebVitals from './reportWebVitals';
const root = ReactDOM.createRoot(document.getElementById('root'));
root.render(
<React.StrictMode>
<App />
</React.StrictMode>
);
reportWebVitals();

View File

@ -0,0 +1,6 @@
const reportWebVitals = (metric) => {
console.log(metric);
};
export default reportWebVitals;

View File

@ -0,0 +1,8 @@
/** @type {import('tailwindcss').Config} */
export default {
content: ["./src/**/*.{js,jsx,ts,tsx,css}"],
theme: {
extend: {},
},
plugins: [],
}

5
requirements.txt Normal file
View File

@ -0,0 +1,5 @@
Flask
Flask-Cors
pandas
scikit-learn
openpyxl