|
|
import React, { Component } from 'react';
|
|
|
import ReactDOM from 'react-dom';
|
|
|
|
|
|
import axios from 'axios';
|
|
|
import Snackbar from 'material-ui/Snackbar';
|
|
|
import Fade from 'material-ui/transitions/Fade';
|
|
|
|
|
|
import update from 'immutability-helper'
|
|
|
|
|
|
import Dialog, {
|
|
|
DialogActions,
|
|
|
DialogContent,
|
|
|
DialogContentText,
|
|
|
DialogTitle,
|
|
|
} from 'material-ui/Dialog';
|
|
|
|
|
|
import Button from 'material-ui/Button';
|
|
|
|
|
|
import EvaluateSuccessEffectDisplay from './EvaluateSuccessEffectDisplay'
|
|
|
|
|
|
import _ from 'lodash'
|
|
|
|
|
|
/*
|
|
|
若干js库
|
|
|
http://inorganik.github.io/countUp.js/
|
|
|
|
|
|
*/
|
|
|
/*
|
|
|
切下一关需要更新:
|
|
|
LeftViewContainer state.gameAnswer
|
|
|
*/
|
|
|
|
|
|
import TPIContext from './TPIContext'
|
|
|
import { EDU_ADMIN, EDU_SHIXUN_MANAGER, EDU_SHIXUN_MEMBER, EDU_CERTIFICATION_TEACHER
|
|
|
, EDU_GAME_MANAGER, EDU_TEACHER, EDU_NORMAL, EDU_BUSINESS} from 'educoder'
|
|
|
import { MuiThemeProvider, createMuiTheme, withStyles } from 'material-ui/styles';
|
|
|
import MUIDialogStyleUtil from '../modules/page/component/MUIDialogStyleUtil'
|
|
|
|
|
|
const styles = MUIDialogStyleUtil.getTwoButtonStyle()
|
|
|
|
|
|
// 主题自定义
|
|
|
const theme = createMuiTheme({
|
|
|
palette: {
|
|
|
primary: {
|
|
|
main: '#4CACFF',
|
|
|
contrastText: 'rgba(255, 255, 255, 0.87)'
|
|
|
},
|
|
|
secondary: { main: '#4CACFF' }, // This is just green.A700 as hex.
|
|
|
},
|
|
|
});
|
|
|
|
|
|
const testSetsExpandedArrayInitVal = [false, false, false, false, false,
|
|
|
false, false, false, false, false,
|
|
|
false, false, false, false, false,
|
|
|
false, false, false, false, false]
|
|
|
window.__fetchAllFlag = false; // 是否调用过fetchAll TODO 如何多次使用provider?
|
|
|
class TPIContextProvider extends Component {
|
|
|
constructor(props) {
|
|
|
super(props)
|
|
|
this.onRunCodeTestFinish = this.onRunCodeTestFinish.bind(this)
|
|
|
this.onRunChooseTestFinish = this.onRunChooseTestFinish.bind(this)
|
|
|
this.testSetUnlock = this.testSetUnlock.bind(this)
|
|
|
|
|
|
this.onTestSetHeaderClick = this.onTestSetHeaderClick.bind(this)
|
|
|
|
|
|
this.onShowPrevStage = this.onShowPrevStage.bind(this)
|
|
|
this.onShowNextStage = this.onShowNextStage.bind(this)
|
|
|
|
|
|
this.readGameAnswer = this.readGameAnswer.bind(this)
|
|
|
this.praisePlus = this.praisePlus.bind(this)
|
|
|
|
|
|
this.onGamePassed = this.onGamePassed.bind(this)
|
|
|
|
|
|
this.onPathChange = this.onPathChange.bind(this)
|
|
|
|
|
|
this.showSnackbar = this.showSnackbar.bind(this)
|
|
|
this.showDialog = this.showDialog.bind(this)
|
|
|
|
|
|
this.onShowUpdateDialog = this.onShowUpdateDialog.bind(this)
|
|
|
this.updateDialogClose = this.updateDialogClose.bind(this)
|
|
|
|
|
|
// this.showEffectDisplay();
|
|
|
|
|
|
this.state = {
|
|
|
loading: true, // 正在加载数据
|
|
|
gDialogOpen: false,
|
|
|
currentGamePassed: false, // 当前game评测通过
|
|
|
currentPassedGameGainGold: 0, // 当前通过的game获得的金币数
|
|
|
currentPassedGameGainExperience: 0, // 当前通过的game获得的经验数
|
|
|
|
|
|
user: {},
|
|
|
challenge: {},
|
|
|
shixun_name: '',
|
|
|
hide_code: false,
|
|
|
|
|
|
showUpdateDialog: false,
|
|
|
|
|
|
testSetsExpandedArray: testSetsExpandedArrayInitVal.slice(0),
|
|
|
}
|
|
|
}
|
|
|
|
|
|
showEffectDisplay = (data) => {
|
|
|
const dom = document.getElementById('picture_display');
|
|
|
window.$(dom).show();
|
|
|
ReactDOM.render(<EvaluateSuccessEffectDisplay type={"qrcode"} {...data} />, dom);
|
|
|
}
|
|
|
|
|
|
onShowUpdateDialog() {
|
|
|
this.setState({showUpdateDialog: true})
|
|
|
}
|
|
|
// updateNowSuccess true 立即更新成功
|
|
|
// TODO updateDialogClose方法名不对, 改为updateDialogCallback
|
|
|
updateDialogClose(nextUpdateSuccess, updateNowSuccess) {
|
|
|
const { myshixun } = this.state;
|
|
|
if (nextUpdateSuccess) {
|
|
|
myshixun.system_tip = true;
|
|
|
}
|
|
|
let { tpm_cases_modified, tpm_modified, tpm_script_modified } = this.state;
|
|
|
if (updateNowSuccess) {
|
|
|
tpm_cases_modified = false;
|
|
|
tpm_modified = false;
|
|
|
tpm_script_modified = false;
|
|
|
}
|
|
|
this.setState({
|
|
|
myshixun,
|
|
|
tpm_cases_modified,
|
|
|
tpm_modified,
|
|
|
tpm_script_modified,
|
|
|
showUpdateDialog: false
|
|
|
})
|
|
|
}
|
|
|
|
|
|
componentWillUnmount() {
|
|
|
this.costTimeInterval && window.clearInterval(this.costTimeInterval)
|
|
|
}
|
|
|
componentDidMount() {
|
|
|
|
|
|
// TODO 登录状态的判断?
|
|
|
// request
|
|
|
// var shixunId = this.props.match.params.shixunId;
|
|
|
var stageId = this.props.match.params.stageId;
|
|
|
|
|
|
window.__fetchAllFlag = false;
|
|
|
this.fetchAll(stageId);
|
|
|
|
|
|
this.costTimeInterval = window.setInterval(()=> {
|
|
|
const { game } = this.state;
|
|
|
if (!game || game.status === 2) { // 已完成的任务不需要计时
|
|
|
return;
|
|
|
}
|
|
|
if (game.cost_time || game.cost_time === 0) {
|
|
|
// game.cost_time += 1;
|
|
|
this.setState({
|
|
|
game: update(game, {cost_time: { $set: (game.cost_time+1) }})
|
|
|
})
|
|
|
}
|
|
|
}, 1000)
|
|
|
|
|
|
// 页面离开时存下用户的任务耗时
|
|
|
|
|
|
window.$(window).unload( ()=>{
|
|
|
this._updateCostTime();
|
|
|
});
|
|
|
}
|
|
|
// force 评测通过后,异步执行该方法,强制同步costTime到服务端
|
|
|
_updateCostTime(async = false, force) {
|
|
|
const { game, loading } = this.state;
|
|
|
// TODO 还有一种情况,通关后cost_time计时停止,没法通过这个判断
|
|
|
if (!force && (loading || !game || game.status === 2)) {
|
|
|
return; // 已完成的任务不需要处理
|
|
|
}
|
|
|
let testPath = ''
|
|
|
if (window.location.port == 3007) {
|
|
|
testPath = 'https://testeduplus2.educoder.net'
|
|
|
}
|
|
|
// var url = `${testPath}/api/v1/games/${ game.identifier }/cost_time`
|
|
|
var url = `${testPath}/api/tasks/${ game.identifier }/cost_time`
|
|
|
window.$.ajax({
|
|
|
type: 'get',
|
|
|
url: url,
|
|
|
async: async, //IMPORTANT, the call will be synchronous
|
|
|
data: {
|
|
|
time: game.cost_time
|
|
|
}
|
|
|
}).done((data) => {
|
|
|
console.log('complete');
|
|
|
});
|
|
|
}
|
|
|
|
|
|
onGamePassed(passed) {
|
|
|
const { game } = this.state
|
|
|
// 随便给个分,以免重新评测时又出现评星组件(注意:目前game.star没有显示在界面上,如果有则不能这么做)
|
|
|
// game.star = 6;
|
|
|
this.setState({
|
|
|
game: update(game, {star: { $set: 6 }}),
|
|
|
currentGamePassed: !!passed
|
|
|
})
|
|
|
}
|
|
|
onTestSetHeaderClick(index) {
|
|
|
// let { testSetsExpandedArray } = this.state;
|
|
|
let testSetsExpandedArray;
|
|
|
// 一次只打开一个
|
|
|
if (this.state.testSetsExpandedArray[index] === false) {
|
|
|
testSetsExpandedArray = testSetsExpandedArrayInitVal.slice(0);
|
|
|
} else {
|
|
|
testSetsExpandedArray = this.state.testSetsExpandedArray.slice(0);
|
|
|
}
|
|
|
testSetsExpandedArray[index] = !testSetsExpandedArray[index];
|
|
|
this.setState({
|
|
|
testSetsExpandedArray,
|
|
|
})
|
|
|
}
|
|
|
|
|
|
onShowPrevStage() {
|
|
|
|
|
|
}
|
|
|
onShowNextStage() {
|
|
|
window.__fetchAllFlag = false;
|
|
|
console.log('onShowNextStage.........')
|
|
|
// this.fetchAll('vznhx7mctwfq')
|
|
|
}
|
|
|
|
|
|
componentWillReceiveProps(newProps, oldProps) {
|
|
|
var newStageId = newProps.match.params.stageId;
|
|
|
if (!this.props || newStageId !== this.props.match.params.stageId) {
|
|
|
window.__fetchAllFlag = false;
|
|
|
this.fetchAll(newStageId)
|
|
|
}
|
|
|
}
|
|
|
|
|
|
|
|
|
// praise_tread/praise_plus?obj_id=569&obj_type=Challenge&horizontal=true&game_praise=true
|
|
|
/*
|
|
|
TODO 旧的接口在未登录时的返回值
|
|
|
//获取登录页面地址
|
|
|
var signinPath = '/';
|
|
|
var htmlvalue = '<div class="task-popup" style="width:480px;"><div class="task-popup-title clearfix"><h3 class="fl color-grey3">提示</h3></div>'+
|
|
|
'<div class="task-popup-content"><p class="task-popup-text-center font-16 mt10 mb10">您还没有登录,请登录后再执行此操作,谢谢!</p></div><div class="task-popup-right-sure clearfix">'+
|
|
|
'<a href="javascript:void(0);" onclick="hideModal();" class="task-btn">取消</a><a href="' + signinPath + '" class="task-btn task-btn-orange ml15">登录</a></div></div>';
|
|
|
pop_box_new(htmlvalue, 480, 182);
|
|
|
*/
|
|
|
praisePlus() {
|
|
|
const { challenge, game } = this.state;
|
|
|
let praise = true;
|
|
|
const url = `/tasks/${game.identifier}/plus_or_cancel_praise.json`
|
|
|
// const url = `/praise_tread/praise_plus?obj_id=${challenge.id}&obj_type=Challenge&horizontal=${praise}&game_praise=true`
|
|
|
axios.post(url)
|
|
|
.then((response) => {
|
|
|
|
|
|
if (response.data) {
|
|
|
const { praise_count, praise } = response.data;
|
|
|
// challenge.praise_count = praise_tread_count;
|
|
|
// challenge.user_praise = praise;
|
|
|
this.setState({ challenge: update(challenge,
|
|
|
{
|
|
|
praise_count: { $set: praise_count },
|
|
|
user_praise: { $set: praise },
|
|
|
})
|
|
|
})
|
|
|
}
|
|
|
|
|
|
})
|
|
|
.catch(function (error) {
|
|
|
console.log(error);
|
|
|
});
|
|
|
}
|
|
|
|
|
|
onPathChange(index) {
|
|
|
let { challenge } = this.state;
|
|
|
// challenge = Object.assign({}, challenge)
|
|
|
// challenge.pathIndex = index;
|
|
|
this.setState({
|
|
|
challenge: update(challenge, {pathIndex: { $set: index }}),
|
|
|
})
|
|
|
// TODO load new path content
|
|
|
}
|
|
|
|
|
|
updateChallengePath = (path) => {
|
|
|
const challenge = this.state.challenge;
|
|
|
if (challenge.path === path) {
|
|
|
return;
|
|
|
}
|
|
|
const { myshixun } = this.state;
|
|
|
// myshixun.system_tip = false;
|
|
|
|
|
|
|
|
|
challenge.path = path;
|
|
|
const newChallenge = this.handleChallengePath(challenge);
|
|
|
this.setState({ challenge: newChallenge,
|
|
|
myshixun: update(myshixun, {system_tip: { $set: false }}),
|
|
|
})
|
|
|
}
|
|
|
|
|
|
handleChallengePath(challenge) {
|
|
|
if (challenge.path && typeof challenge.path === "string") { // 多path的处理
|
|
|
let path = challenge.path.split(';');
|
|
|
_.remove(path, (item)=> !item)
|
|
|
if (path.length > 1) {
|
|
|
challenge.path = path;
|
|
|
challenge.multiPath = true;
|
|
|
} else {
|
|
|
challenge.path = challenge.path.replace(';', '').trim() // 多path 改为单path 出现了 aaa.java;的情况
|
|
|
challenge.multiPath = false;
|
|
|
}
|
|
|
}
|
|
|
challenge.pathIndex = 0;
|
|
|
return challenge;
|
|
|
}
|
|
|
|
|
|
newResData2OldResData(newResData) {
|
|
|
newResData.latest_output = newResData.last_compile_output
|
|
|
// newResData.power
|
|
|
newResData.record = newResData.record_onsume_time
|
|
|
|
|
|
// 老版用的hide_code
|
|
|
newResData.hide_code = newResData.shixun.hide_code;
|
|
|
|
|
|
newResData.image_url = newResData.user.image_url
|
|
|
newResData.grade = newResData.user.grade
|
|
|
newResData.user_url = newResData.user.user_url
|
|
|
newResData.username = newResData.user.name
|
|
|
|
|
|
newResData.output_sets = {}
|
|
|
// newResData.output_sets.had_test_count = newResData.test_sets_count
|
|
|
newResData.output_sets.test_sets = newResData.test_sets // JSON.stringify()
|
|
|
newResData.output_sets.test_sets_count = newResData.test_sets_count
|
|
|
// newResData.output_sets.had_passed_testsests_error_count = newResData.sets_error_count
|
|
|
newResData.output_sets.had_passed_testsests_error_count = newResData.test_sets_count
|
|
|
- newResData.sets_error_count
|
|
|
// allowed_hidden_testset
|
|
|
// sets_error_count
|
|
|
// test_sets_count
|
|
|
// test_sets
|
|
|
// had_passed_testsests_error_count
|
|
|
// test_sets
|
|
|
// test_sets
|
|
|
|
|
|
return newResData
|
|
|
}
|
|
|
// 将若干数据重新组织一下
|
|
|
_handleResponseData(resData_arg) {
|
|
|
const resData = this.newResData2OldResData(Object.assign({}, resData_arg))
|
|
|
let challenge = resData.challenge;
|
|
|
challenge.isHtml = false;
|
|
|
challenge.isWeb = false;
|
|
|
challenge.isAndroid = false;
|
|
|
challenge.showLanguagePictrue = false;
|
|
|
challenge.hasAnswer = resData.has_answer;
|
|
|
|
|
|
let output_sets = resData.output_sets;
|
|
|
if (resData.st === 0) { // 代码题
|
|
|
challenge = this.handleChallengePath(challenge)
|
|
|
|
|
|
const mirror_name = (resData.mirror_name && resData.mirror_name.join)
|
|
|
? resData.mirror_name.join(';') : (resData.mirror_name || '');
|
|
|
if (mirror_name.indexOf('Html') !== -1) {
|
|
|
challenge.isHtml = true;
|
|
|
challenge.showLanguagePictrue = true;
|
|
|
} else if (mirror_name.indexOf('Web') !== -1 || mirror_name.indexOf('JFinal') !== -1) {
|
|
|
challenge.isWeb = true;
|
|
|
} else if (mirror_name.indexOf('Android') !== -1) {
|
|
|
challenge.isAndroid = true;
|
|
|
}
|
|
|
|
|
|
if (output_sets && output_sets.test_sets && typeof output_sets.test_sets == 'string') {
|
|
|
const test_sets_array = JSON.parse("[" + output_sets.test_sets + "]");
|
|
|
output_sets.test_sets_array = test_sets_array;
|
|
|
} else {
|
|
|
output_sets.test_sets_array = output_sets.test_sets
|
|
|
}
|
|
|
|
|
|
} else { // 选择题
|
|
|
// 选择题题干markdown初始化
|
|
|
const $ = window.$
|
|
|
window.setTimeout(()=>{
|
|
|
var lens = $("#choiceRepositoryView textarea").length;
|
|
|
|
|
|
for(var i = 1; i <= lens; i++){
|
|
|
window.editormd.markdownToHTML("choose_subject_" + i, {
|
|
|
htmlDecode: "style,script,iframe", // you can filter tags decode
|
|
|
taskList: true,
|
|
|
tex: true, // 数学公式
|
|
|
// flowChart: true, // 默认不解析
|
|
|
// sequenceDiagram: true // 默认不解析
|
|
|
});
|
|
|
}
|
|
|
}, 400)
|
|
|
}
|
|
|
challenge.user_praise = resData.user_praise;
|
|
|
challenge.praise_count = resData.praise_count;
|
|
|
challenge.showWebDisplayButton = false;
|
|
|
resData.challenge = challenge;
|
|
|
|
|
|
// 将一些属性写到game上
|
|
|
let game = resData.game;
|
|
|
game.prev_game = resData.prev_game;
|
|
|
game.next_game = resData.next_game;
|
|
|
resData.game = game;
|
|
|
|
|
|
const { tpm_cases_modified, tpm_modified, tpm_script_modified, myshixun } = resData;
|
|
|
if (myshixun.system_tip) {
|
|
|
// system_tip为true的时候 不弹框提示用户更新
|
|
|
resData.showUpdateDialog = false
|
|
|
} else {
|
|
|
let needUpdateScript = (tpm_modified || tpm_script_modified) && challenge.st === 0;
|
|
|
resData.showUpdateDialog = needUpdateScript || tpm_cases_modified
|
|
|
}
|
|
|
|
|
|
/**
|
|
|
email: "721773699@qq.com"
|
|
|
grade: 213996
|
|
|
identity: 1
|
|
|
image_url: "avatars/User/1"
|
|
|
login: "innov"
|
|
|
name: "Coder"
|
|
|
user_url: "/users/innov"
|
|
|
*/
|
|
|
let user = resData.user;
|
|
|
user.username = resData.user.name;
|
|
|
user.user_url = `/user/${resData.user.login}`;
|
|
|
// user.image_url = resData.image_url;
|
|
|
user.is_teacher = resData.is_teacher;
|
|
|
resData.user = user;
|
|
|
this._handleUserAuthor(resData)
|
|
|
// TODO 测试
|
|
|
// resData.power = 0;
|
|
|
|
|
|
this.setState({
|
|
|
...resData,
|
|
|
|
|
|
currentGamePassed: false,
|
|
|
loading: false,
|
|
|
testSetsExpandedArray: testSetsExpandedArrayInitVal.slice(0),
|
|
|
})
|
|
|
|
|
|
window.document.title = resData.shixun.name
|
|
|
|
|
|
window.__myshixun = resData.myshixun; // tpi_html_show需要用到
|
|
|
}
|
|
|
_handleUserAuthor(resData) {
|
|
|
// tpi tpm权限控制
|
|
|
// const EDU_ADMIN = 1 // 超级管理员
|
|
|
// const EDU_SHIXUN_MANAGER = 2 // 实训管理员
|
|
|
// const EDU_SHIXUN_MEMBER = 3 // 实训成员
|
|
|
// const EDU_CERTIFICATION_TEACHER = 4 // 平台认证的老师
|
|
|
// const EDU_GAME_MANAGER = 5 // TPI的创建者
|
|
|
// const EDU_TEACHER = 6 // 平台老师,但是未认证
|
|
|
// const EDU_NORMAL = 7 // 普通用户
|
|
|
|
|
|
|
|
|
/**
|
|
|
EDU_ADMIN = 1 # 超级管理员
|
|
|
EDU_BUSINESS = 2 # 运营人员
|
|
|
EDU_SHIXUN_MANAGER = 3 # 实训管理员
|
|
|
EDU_SHIXUN_MEMBER = 4 # 实训成员
|
|
|
EDU_CERTIFICATION_TEACHER = 5 # 平台认证的老师
|
|
|
EDU_GAME_MANAGER = 6 # TPI的创建者
|
|
|
EDU_TEACHER = 7 # 平台老师,但是未认证
|
|
|
EDU_NORMAL = 8 # 普通用户
|
|
|
*/
|
|
|
|
|
|
// myshixun_manager power is_teacher
|
|
|
resData.power = 0
|
|
|
resData.myshixun_manager = false
|
|
|
// resData.is_teacher = false
|
|
|
|
|
|
if (resData.user.identity === EDU_ADMIN) {
|
|
|
resData.power = 1
|
|
|
resData.myshixun_manager = true
|
|
|
} else if (resData.user.identity === EDU_BUSINESS) {
|
|
|
resData.power = 1
|
|
|
resData.myshixun_manager = true
|
|
|
} else if (resData.user.identity === EDU_SHIXUN_MANAGER) {
|
|
|
resData.power = 1
|
|
|
resData.myshixun_manager = true
|
|
|
} else if (resData.user.identity === EDU_SHIXUN_MEMBER) {
|
|
|
resData.power = 1
|
|
|
resData.myshixun_manager = true
|
|
|
} else if (resData.user.identity === EDU_CERTIFICATION_TEACHER) {
|
|
|
resData.power = 1
|
|
|
// resData.is_teacher = true
|
|
|
|
|
|
} else if (resData.user.identity === EDU_TEACHER) {
|
|
|
// resData.is_teacher = true
|
|
|
} else if (resData.user.identity === EDU_NORMAL) {
|
|
|
|
|
|
}
|
|
|
return resData
|
|
|
}
|
|
|
|
|
|
fetchAll(stageId) {
|
|
|
|
|
|
if (window.__fetchAllFlag == true ) {
|
|
|
console.log('TPIContextProvider call fetchAll repeatly!')
|
|
|
return;
|
|
|
}
|
|
|
// 切换关卡的时候,同步costTime
|
|
|
this._updateCostTime(true);
|
|
|
|
|
|
if (!stageId) {
|
|
|
// stageId = 'zl6kx8f7vfpo';
|
|
|
// http://localhost:3000/myshixuns/so5w6iap97/stages/zl6kx8f7vfpo
|
|
|
}
|
|
|
|
|
|
// var url = `/api/v1/games/${stageId}`
|
|
|
var url = `/tasks/${stageId}.json`
|
|
|
|
|
|
// {"status":1,"message":"undefined method `authenticate!' for #<Grape::Endpoint:0xc8c91c0>"}
|
|
|
window.__fetchAllFlag = true;
|
|
|
|
|
|
|
|
|
this.setState({
|
|
|
loading: true,
|
|
|
currentGamePassed: false, // 切换game时重置passed字段
|
|
|
})
|
|
|
|
|
|
// test
|
|
|
// var data = {"st":0,"discusses_count":12,"game_count":6,"record_onsume_time":5.303,"prev_game":"q67plhfjaogy","next_game":"lfrwm2ohiate","praise_count":0,"user_praise":false,"time_limit":180,"tomcat_url":"http://47.98.226.234","is_teacher":true,"myshixun_manager":false,"game":{"id":1964918,"myshixun_id":510423,"user_id":73892,"created_at":"2019-06-24T11:22:58.000+08:00","updated_at":"2019-06-25T11:15:48.000+08:00","status":0,"final_score":0,"challenge_id":573,"open_time":"2019-06-24T11:22:58.000+08:00","identifier":"yrsxolqk6zcp","answer_open":0,"end_time":null,"retry_status":0,"resubmit_identifier":null,"test_sets_view":false,"picture_path":null,"accuracy":null,"modify_time":null,"star":0,"cost_time":3966,"evaluate_count":1,"answer_deduction":0},"challenge":{"id":573,"shixun_id":186,"subject":"应用模型做预测","position":4,"task_pass":"####本关任务\r\n本关卡学习如何应用机器学习模型来做预测。\r\n\r\n####相关知识\r\n在前一关卡中,我们一起探讨了机器学习的一般原理,并建立了一个非常简单的电影评分模型Model 0:\r\n\r\n评分 = **大众对电影的平均评分** + **用户个人的给分偏好** + **电影的评分偏好**\r\n\r\n针对这个模型,我们设计了一个非常朴素的预测模型Baseline,直接从数据集中统计得到上述三个参数的值。\r\n\r\n在本关卡中,我们将应用这个模型对用户和电影的评分做出预测。\r\n\r\n####编程要求\r\n回顾Model 0的预测评分公式:\r\n```latex\r\nf(u,m)=g+\\alpha(u)+\\beta(m)\r\n```\r\n\r\n我们的Baseline模型得到了$$g$$、$$\\alpha$$和$$\\beta$$三种参数,下面我们实现predict函数,来对测试数据集中未知的用户电影评分进行预测,需要填充的代码块如下:\r\n```python\r\n# -*- coding:utf-8 -*-\r\n\r\ndef predict(g, alpha, beta, test_data):\r\n\t\"\"\"预测用户对电影的评分\r\n\t参数:\r\n\t\tg - 浮点数,模型参数平均电影评分\r\n\t\talpha - 浮点数组,用户评分偏差参数数组\r\n\t\tbeta - 浮点数组,电影评分偏差参数数组\r\n\t\ttest_data - Pandas的DataFrame对象,有两列'user','movie',是测试数据集\r\n\t返回值:\r\n\t\tret - 浮点数数组,预测的评分数组,举例ret[10],表示第10组用户和电影对的评分值\r\n\t\"\"\"\t\r\n\tret = []\r\n\tN = len(alpha)\r\n\tM = len(beta)\r\n\t\r\n\t# 请在此添加实现代码\r\n\t#********** Begin *********#\r\n\t\r\n\t#********** End *********#\r\n\t\r\n\treturn ret\r\n```\r\n\r\n####本关任务\r\n本关卡的测试数据来自内置测试文件,平台将比对您所编写函数的预测评分与正确评分,只有所有数据全部计算正确才能进入下一关。","score":500,"path":"src/step4/doprediction.py","st":0,"web_route":null,"modify_time":null},"shixun":{"id":186,"name":"理解机器学习基本概念:从电影评分预测讲起","user_id":24758,"gpid":3676,"visits":622,"created_at":"2017-08-25T18:07:41.000+08:00","updated_at":"2019-06-02T11:05:20.000+08:00","status":2,"language":"MachineLearning","authentication":false,"identifier":"58DRWG63","trainee":3,"major_id":635,"webssh":0,"homepage_show":false,"hidden":false,"fork_from":null,"can_copy":true,"modify_time":"2017-09-29T21:42:16.000+08:00","reset_time":"2017-09-29T21:42:16.000+08:00","publish_time":"2017-09-29T10:58:13.000+08:00","closer_id":null,"end_time":null,"git_url":"educoder/58drwg63","vnc":false,"myshixuns_count":318,"challenges_count":6,"use_scope":0,"mirror_script_id":0,"image_text":null,"code_hidden":false,"task_pass":false,"exec_time":180,"test_set_permission":true,"sigle_training":false,"hide_code":false,"multi_webssh":false,"excute_time":null,"repo_name":"educoder/58drwg63","averge_star":4.9,"opening_time":null,"users_count":10,"forbid_copy":false,"pod_life":0},"myshixun":{"id":510423,"shixun_id":186,"is_public":true,"user_id":73892,"gpid":null,"created_at":"2019-06-24T11:22:55.000+08:00","updated_at":"2019-06-24T13:56:40.000+08:00","status":0,"identifier":"7pkwxim9eh","commit_id":"ff7c6652fdfdf62eaa1316d39400ebdbd6cb81fb","modify_time":"2017-09-29T21:42:16.000+08:00","reset_time":"2017-09-29T21:42:16.000+08:00","system_tip":false,"git_url":null,"onclick_time":"2019-06-24T11:22:55.000+08:00","repo_name":"p35840769/7pkwxim9eh20190624112255"},"user":{"user_id":73892,"login":"p35840769","name":"韩半安","grade":6895,"image_url":"avatars/User/b","school":"国防科技大学","identity":6},"tpm_modified":false,"tpm_cases_modified":false,"mirror_name":["MachineLearning"],"has_answer":true,"test_sets":[{"is_public":true,"result":false,"input":"771 253 360 99 8 759 976 387 873 829 437 53 854 148 447 179 246 810 158 653 583 929 691 892 263 230 637 221 7 652 127 965 767","output":"3.577 -0.329 2.648 4.727 4.351 2.616 3.496 3.059 3.470 3.166 3.064 2.716 3.712 4.003 3.064 3.462 4.004 2.067 3.860 0.121 3.807 3.735 4.230 3.137 4.431 2.468 4.018 5.218 4.351 4.121 4.050 4.587 3.777","actual_output":"Traceback (most recent call last):\r\n File \"src/step4/main.py\", line 3, in \u003cmodule\u003e\r\n from doprediction import predict\r\nImportError: cannot import name 'predict'\r\n","compile_success":1},{"is_public":false,"result":false,"compile_success":1}],"allowed_unlock":true,"last_compile_output":"共有2组测试集,其中有2组测试结果不匹配。详情如下:","test_sets_count":2,"sets_error_count":2}
|
|
|
// data.shixun.vnc = true
|
|
|
// data.vnc_url= "http://47.96.157.89:54144/vnc_lite.html?password=headless"
|
|
|
|
|
|
// this._handleResponseData(data)
|
|
|
// return
|
|
|
|
|
|
axios.get(url, {
|
|
|
// https://stackoverflow.com/questions/48861290/the-value-of-the-access-control-allow-origin-header-in-the-response-must-not-b
|
|
|
// withCredentials: true,
|
|
|
})
|
|
|
.then((response) => {
|
|
|
// {"status":1,"message":"Unauthorized. \u7528\u6237\u8ba4\u8bc1\u5931\u8d25."}
|
|
|
|
|
|
if (response.data.status == 403) {
|
|
|
window.location.href = "/403";
|
|
|
return;
|
|
|
}
|
|
|
if (response.data.status == 404) {
|
|
|
window.location.href = '/myshixuns/not_found'
|
|
|
return;
|
|
|
}
|
|
|
|
|
|
this._handleResponseData(response.data)
|
|
|
|
|
|
})
|
|
|
.catch(function (error) {
|
|
|
console.log(error);
|
|
|
});
|
|
|
|
|
|
}
|
|
|
|
|
|
readGameAnswer(resData) {
|
|
|
// game.final_score = resData.final_score;
|
|
|
if (resData.final_score) {
|
|
|
var game = this.state.game;
|
|
|
this.setState({
|
|
|
game: update(game, {final_score: { $set: resData.final_score }}),
|
|
|
grade: resData.grade
|
|
|
})
|
|
|
} else {
|
|
|
this.setState({
|
|
|
grade: resData.grade
|
|
|
})
|
|
|
}
|
|
|
|
|
|
}
|
|
|
closeTaskResultLayer() {
|
|
|
this.setState({
|
|
|
currentGamePassed: false
|
|
|
})
|
|
|
}
|
|
|
onRunChooseTestFinish(response) {
|
|
|
const { test_sets, challenge_chooses_count, choose_correct_num, grade, experience, gold, had_submmit, next_game } = response;
|
|
|
response.had_submmit = true; // 是否已提交
|
|
|
const { game } = this.state;
|
|
|
let currentGamePassed = false
|
|
|
if (challenge_chooses_count === choose_correct_num) {
|
|
|
game.status = 2;
|
|
|
game.next_game = next_game;
|
|
|
|
|
|
currentGamePassed = true;
|
|
|
|
|
|
|
|
|
this._updateCostTime(true, true);
|
|
|
}
|
|
|
this.setState({
|
|
|
choose_test_cases: response,
|
|
|
grade: grade,
|
|
|
|
|
|
game,
|
|
|
next_game,
|
|
|
currentGamePassed: currentGamePassed,
|
|
|
currentPassedGameGainGold: gold,
|
|
|
currentPassedGameGainExperience: experience,
|
|
|
})
|
|
|
}
|
|
|
language_display(data) {
|
|
|
const { game, tomcat_url } = this.state;
|
|
|
const $ = window.$;
|
|
|
const challenge = Object.assign({}, this.state.challenge)
|
|
|
if(challenge.isWeb && data.port != -1) {
|
|
|
// var $result = $("#php_display");
|
|
|
challenge.showWebDisplayButton = true; // ActionView处是否出现查看效果按钮
|
|
|
const path = challenge.web_route || challenge.path
|
|
|
const webDisplayUrl = `${tomcat_url}:${data.port}/${path}`
|
|
|
challenge.webDisplayUrl = webDisplayUrl
|
|
|
challenge.showLanguagePictrue = true; // 评测通过弹出层是否出现查看效果按钮
|
|
|
}
|
|
|
// else if(challenge.isAndroid && data.picture != 0){
|
|
|
// // https://www.educoder.net/shixuns/qrcode?game_id=218589&_=1525571882782
|
|
|
// $.ajax({
|
|
|
// url: `/shixuns/qrcode?game_id=${game.id}`,
|
|
|
// dataType: 'script'
|
|
|
// });
|
|
|
// challenge.showLanguagePictrue = true;
|
|
|
// }
|
|
|
else if(data.picture != 0){
|
|
|
// 对应服务端erb文件为 _picture_display.html.erb
|
|
|
// $.ajax({
|
|
|
// url: "/users/picture_show?game_id="+data.picture,
|
|
|
// cache: false,
|
|
|
// dataType: 'script'
|
|
|
// });
|
|
|
|
|
|
/**
|
|
|
{
|
|
|
"type": "image",
|
|
|
"orignal_picture": [],
|
|
|
"user_picture": [],
|
|
|
"answer_picture": []
|
|
|
}
|
|
|
*/
|
|
|
const url = `/tasks/${game.identifier}/picture_display.json`
|
|
|
axios.get(url)
|
|
|
.then((response) => {
|
|
|
// response.data.type qrcode_str
|
|
|
this.showEffectDisplay(response.data)
|
|
|
})
|
|
|
|
|
|
challenge.showLanguagePictrue = true;
|
|
|
}
|
|
|
this.setState({
|
|
|
challenge
|
|
|
})
|
|
|
}
|
|
|
onRunCodeTestFinish(response) {
|
|
|
console.log('onRunCodeTestFinish', response)
|
|
|
const { test_sets, test_sets_count, test_sets_hidden_count, test_sets_public_count
|
|
|
, had_test_count, had_passed_testsests_error_count, had_passed_testsests_hidden_count
|
|
|
, had_passed_testsests_public_count, final_score, gold, experience, latest_output, status
|
|
|
, had_done, score, tag_count, power, record, next_game, grade, picture,
|
|
|
sets_error_count, last_compile_output, record_consume_time} = response;
|
|
|
|
|
|
const { game } = this.state;
|
|
|
|
|
|
const currentGamePassed = this.props.game !== 2 && status === 2
|
|
|
|
|
|
currentGamePassed && this.language_display(response);
|
|
|
|
|
|
// 评测通过了,立即同步costTime
|
|
|
currentGamePassed && this._updateCostTime(true, true);
|
|
|
|
|
|
if (currentGamePassed) {
|
|
|
game.status = 2;
|
|
|
game.next_game = next_game;
|
|
|
|
|
|
} else {
|
|
|
this.showDialog({
|
|
|
contentText: <div>
|
|
|
<div>评测未通过</div>
|
|
|
<div>详情请参见“测试结果”</div>
|
|
|
</div>,
|
|
|
isSingleButton: true
|
|
|
})
|
|
|
}
|
|
|
|
|
|
const output_sets = {
|
|
|
"test_sets": test_sets,
|
|
|
"test_sets_array": test_sets,
|
|
|
"had_test_count": had_test_count || test_sets_count,
|
|
|
"test_sets_count": test_sets_count,
|
|
|
// "had_passed_testsests_error_count": had_passed_testsests_error_count,
|
|
|
"had_passed_testsests_error_count": test_sets_count - sets_error_count,
|
|
|
"test_sets_hidden_count": test_sets_hidden_count,
|
|
|
"test_sets_public_count": test_sets_public_count,
|
|
|
"had_passed_testsests_hidden_count": had_passed_testsests_hidden_count,
|
|
|
"had_passed_testsests_public_count": had_passed_testsests_public_count
|
|
|
};
|
|
|
// if (output_sets && output_sets.test_sets) {
|
|
|
// const test_sets_array = JSON.parse("[" + output_sets.test_sets + "]");
|
|
|
// output_sets.test_sets_array = test_sets_array;
|
|
|
// }
|
|
|
|
|
|
|
|
|
this.setState({
|
|
|
testSetsExpandedArray: testSetsExpandedArrayInitVal.slice(0), // 重置测试集展开状态
|
|
|
currentGamePassed,
|
|
|
currentPassedGameGainGold: gold,
|
|
|
currentPassedGameGainExperience: experience,
|
|
|
|
|
|
output_sets,
|
|
|
game,
|
|
|
next_game,
|
|
|
|
|
|
latest_output: last_compile_output,
|
|
|
record: record_consume_time,
|
|
|
grade,
|
|
|
had_done,
|
|
|
|
|
|
})
|
|
|
}
|
|
|
resetTestSetsExpandedArray = () => {
|
|
|
this.setState({
|
|
|
testSetsExpandedArray: testSetsExpandedArrayInitVal.slice(0), // 重置测试集展开状态
|
|
|
})
|
|
|
}
|
|
|
|
|
|
testSetUnlock() {
|
|
|
const { game } = this.state;
|
|
|
const url = `/v1/games/${game.identifier}/check_test_sets.json`
|
|
|
axios.get(url, {
|
|
|
withCredentials: true,
|
|
|
})
|
|
|
.then((response) => {
|
|
|
// TODO status -2 重复操作,直接解锁
|
|
|
if (response.data.test_sets == -1) {
|
|
|
console.error('testSetUnlock失败!')
|
|
|
this.showSnackbar(response.data.message)
|
|
|
return;
|
|
|
} else {
|
|
|
// 被扣除的金币,是负数
|
|
|
const deltaScore = response.data.score;
|
|
|
// output_sets
|
|
|
let { output_sets } = this.state;
|
|
|
output_sets = Object.assign({}, output_sets);
|
|
|
const test_sets_array = JSON.parse("[" + response.data.test_sets + "]");
|
|
|
output_sets.test_sets_array = test_sets_array;
|
|
|
this.setState({
|
|
|
output_sets: output_sets,
|
|
|
grade: this.state.grade + deltaScore,
|
|
|
game : update(game, {test_sets_view: { $set: true }}),
|
|
|
testSetsExpandedArray: testSetsExpandedArrayInitVal.slice(0)
|
|
|
})
|
|
|
this.handleGdialogClose();
|
|
|
}
|
|
|
|
|
|
})
|
|
|
.catch(function (error) {
|
|
|
console.log(error);
|
|
|
});
|
|
|
}
|
|
|
|
|
|
handleSnackbarClose() {
|
|
|
this.setState({
|
|
|
snackbarOpen: false,
|
|
|
snackbarVertical: '',
|
|
|
snackbarHorizontal: '',
|
|
|
})
|
|
|
}
|
|
|
// 全局的snackbar this.props.showSnackbar调用即可
|
|
|
showSnackbar(text, vertical, horizontal) {
|
|
|
this.setState({
|
|
|
snackbarOpen: true,
|
|
|
snackbarText: text,
|
|
|
snackbarVertical: vertical,
|
|
|
snackbarHorizontal: horizontal,
|
|
|
})
|
|
|
}
|
|
|
|
|
|
/*
|
|
|
TODO 写成HOC组件,更好复用
|
|
|
全局的Dialog this.props.showDialog调用即可
|
|
|
@param contentText dialog显示的提示文本
|
|
|
@param callback 确定按钮回调方法
|
|
|
@param moreButtonsRender 除了“确定”、“取消”按钮外的其他按钮
|
|
|
@param okButtonText “确定”按钮显示文本,如 继续查看
|
|
|
*/
|
|
|
showDialog(params) {
|
|
|
const { contentText, callback, moreButtonsRender, okButtonText, isSingleButton } = params;
|
|
|
|
|
|
this.dialogOkCallback = callback;
|
|
|
this.moreButtonsRender = moreButtonsRender
|
|
|
this.okButtonText = okButtonText;
|
|
|
this.isSingleButton = isSingleButton;
|
|
|
this.setState({
|
|
|
gDialogOpen: true,
|
|
|
gDialogContentText: contentText
|
|
|
})
|
|
|
}
|
|
|
onGdialogOkBtnClick() {
|
|
|
|
|
|
this.dialogOkCallback && this.dialogOkCallback();
|
|
|
// this.setState({
|
|
|
// gDialogOpen: true
|
|
|
// })
|
|
|
}
|
|
|
handleGdialogClose = () => {
|
|
|
this.setState({
|
|
|
gDialogOpen: false
|
|
|
})
|
|
|
}
|
|
|
render() {
|
|
|
const { classes } = this.props;
|
|
|
return (
|
|
|
<TPIContext.Provider
|
|
|
value={{
|
|
|
...this.state,
|
|
|
resetTestSetsExpandedArray: this.resetTestSetsExpandedArray,
|
|
|
onRunCodeTestFinish: this.onRunCodeTestFinish,
|
|
|
onRunChooseTestFinish: this.onRunChooseTestFinish,
|
|
|
testSetUnlock: this.testSetUnlock,
|
|
|
|
|
|
onTestSetHeaderClick: this.onTestSetHeaderClick,
|
|
|
|
|
|
readGameAnswer: this.readGameAnswer,
|
|
|
|
|
|
onShowPrevStage: this.onShowPrevStage,
|
|
|
onShowNextStage: this.onShowNextStage,
|
|
|
|
|
|
praisePlus: this.praisePlus,
|
|
|
onGamePassed: this.onGamePassed,
|
|
|
closeTaskResultLayer: () => this.closeTaskResultLayer(),
|
|
|
|
|
|
onPathChange: this.onPathChange,
|
|
|
updateChallengePath: this.updateChallengePath,
|
|
|
|
|
|
showSnackbar: this.showSnackbar,
|
|
|
showDialog: this.showDialog,
|
|
|
handleGdialogClose: () => this.handleGdialogClose(),
|
|
|
|
|
|
onShowUpdateDialog: this.onShowUpdateDialog,
|
|
|
updateDialogClose: this.updateDialogClose,
|
|
|
|
|
|
match: this.props.match
|
|
|
}}
|
|
|
>
|
|
|
<Dialog
|
|
|
id="tpi-dialog"
|
|
|
open={this.state.gDialogOpen}
|
|
|
onClose={() => this.handleGdialogClose()}
|
|
|
>
|
|
|
<DialogTitle id="alert-dialog-title">{"提示"}</DialogTitle>
|
|
|
<DialogContent id="dialog-content">
|
|
|
<DialogContentText id="alert-dialog-description" style={{textAlign: 'center'}}>
|
|
|
{this.state.gDialogContentText}
|
|
|
</DialogContentText>
|
|
|
</DialogContent>
|
|
|
{/* mb20 加了有样式问题 */}
|
|
|
<DialogActions className={""} id="dialog-actions">
|
|
|
{ this.isSingleButton ? <div className="task-popup-submit clearfix"
|
|
|
style={{ textAlign: 'center' }}>
|
|
|
<a className="task-btn task-btn-orange"
|
|
|
onClick={this.handleGdialogClose}
|
|
|
>知道啦</a>
|
|
|
</div> :
|
|
|
<React.Fragment>
|
|
|
<Button onClick={() => this.handleGdialogClose()} color="primary"
|
|
|
className={`${classes.button} ${classes.buttonGray}`}>
|
|
|
关闭
|
|
|
</Button>
|
|
|
<Button variant="raised" className={`${classes.button}`}
|
|
|
onClick={() => this.onGdialogOkBtnClick() } color="primary" autoFocus>
|
|
|
{ this.okButtonText ? this.okButtonText : '确定' }
|
|
|
</Button>
|
|
|
</React.Fragment> }
|
|
|
{this.moreButtonsRender && this.moreButtonsRender()}
|
|
|
</DialogActions>
|
|
|
</Dialog>
|
|
|
|
|
|
<Snackbar
|
|
|
className={"rootSnackbar"}
|
|
|
open={this.state.snackbarOpen}
|
|
|
autoHideDuration={3000}
|
|
|
anchorOrigin={{ vertical: this.state.snackbarVertical || 'top'
|
|
|
, horizontal: this.state.snackbarHorizontal || 'center' }}
|
|
|
onClose={() => this.handleSnackbarClose()}
|
|
|
transition={Fade}
|
|
|
SnackbarContentProps={{
|
|
|
'aria-describedby': 'message-id',
|
|
|
}}
|
|
|
resumeHideDuration={2000}
|
|
|
message={<span id="message-id">{this.state.snackbarText}</span>}
|
|
|
/>
|
|
|
{this.props.children}
|
|
|
</TPIContext.Provider>
|
|
|
)
|
|
|
}
|
|
|
}
|
|
|
|
|
|
export default withStyles(styles) (TPIContextProvider);
|
|
|
|
|
|
|
|
|
|