make leaderboard table show agent ranking info; add total row info in query api

This commit is contained in:
Songyi Huang 2020-08-23 23:12:05 -07:00
parent 0cc89a3b55
commit b7b722761e
3 changed files with 178 additions and 99 deletions

View File

@ -48,11 +48,12 @@ PAGE_FIELDS = ['elements_every_page', 'page_index']
def _get_page(result, elements_every_page, page_index): def _get_page(result, elements_every_page, page_index):
elements_every_page = int(elements_every_page) elements_every_page = int(elements_every_page)
page_index = int(page_index) page_index = int(page_index)
total_row = len(result)
total_page = math.ceil(len(result) / float(elements_every_page)) total_page = math.ceil(len(result) / float(elements_every_page))
begin = page_index * elements_every_page begin = page_index * elements_every_page
end = min((page_index+1) * elements_every_page, len(result)) end = min((page_index+1) * elements_every_page, len(result))
result = result[begin:end] result = result[begin:end]
return result, total_page return result, total_page, total_row
def replay(request): def replay(request):
if request.method == 'GET': if request.method == 'GET':
@ -70,9 +71,9 @@ def query_game(request):
return HttpResponse(json.dumps({'value': -1, 'info': 'elements_every_page and page_index should be given'})) return HttpResponse(json.dumps({'value': -1, 'info': 'elements_every_page and page_index should be given'}))
filter_dict = {key: request.GET.get(key) for key in dict(request.GET).keys() if key not in PAGE_FIELDS} filter_dict = {key: request.GET.get(key) for key in dict(request.GET).keys() if key not in PAGE_FIELDS}
result = Game.objects.filter(**filter_dict).order_by('index') result = Game.objects.filter(**filter_dict).order_by('index')
result, total_page = _get_page(result, request.GET['elements_every_page'], request.GET['page_index']) result, total_page, total_row = _get_page(result, request.GET['elements_every_page'], request.GET['page_index'])
result = serializers.serialize('json', result, fields=('name', 'index', 'agent0', 'agent1', 'win', 'payoff')) result = serializers.serialize('json', result, fields=('name', 'index', 'agent0', 'agent1', 'win', 'payoff'))
return HttpResponse(json.dumps({'value': 0, 'data': json.loads(result), 'total_page': total_page})) return HttpResponse(json.dumps({'value': 0, 'data': json.loads(result), 'total_page': total_page, 'total_row': total_row}))
def query_payoff(request): def query_payoff(request):
if request.method == 'GET': if request.method == 'GET':
@ -89,8 +90,8 @@ def query_agent_payoff(request):
return HttpResponse(json.dumps({'value': -2, 'info': 'name should be given'})) return HttpResponse(json.dumps({'value': -2, 'info': 'name should be given'}))
result = list(Payoff.objects.filter(name=request.GET['name']).values('agent0').annotate(payoff = Avg('payoff')).order_by('-payoff')) result = list(Payoff.objects.filter(name=request.GET['name']).values('agent0').annotate(payoff = Avg('payoff')).order_by('-payoff'))
print(result) print(result)
result, total_page = _get_page(result, request.GET['elements_every_page'], request.GET['page_index']) result, total_page, total_row = _get_page(result, request.GET['elements_every_page'], request.GET['page_index'])
return HttpResponse(json.dumps({'value': 0, 'data': result, 'total_page': total_page})) return HttpResponse(json.dumps({'value': 0, 'data': result, 'total_page': total_page, 'total_row': total_row}))
@transaction.atomic @transaction.atomic
def launch(request): def launch(request):

View File

@ -15,7 +15,7 @@ import LinearProgress from '@material-ui/core/LinearProgress';
import PlayArrowRoundedIcon from '@material-ui/icons/PlayArrowRounded'; import PlayArrowRoundedIcon from '@material-ui/icons/PlayArrowRounded';
import PauseCircleOutlineRoundedIcon from '@material-ui/icons/PauseCircleOutlineRounded'; import PauseCircleOutlineRoundedIcon from '@material-ui/icons/PauseCircleOutlineRounded';
import ReplayRoundedIcon from '@material-ui/icons/ReplayRounded'; import ReplayRoundedIcon from '@material-ui/icons/ReplayRounded';
import NotInterestedIcon from '@material-ui/icons/NotInterested'; // import NotInterestedIcon from '@material-ui/icons/NotInterested';
import SkipNextIcon from '@material-ui/icons/SkipNext'; import SkipNextIcon from '@material-ui/icons/SkipNext';
import SkipPreviousIcon from '@material-ui/icons/SkipPrevious'; import SkipPreviousIcon from '@material-ui/icons/SkipPrevious';
import DialogTitle from "@material-ui/core/DialogTitle"; import DialogTitle from "@material-ui/core/DialogTitle";
@ -281,30 +281,30 @@ class DoudizhuGameView extends React.Component {
computeProbabilityItem(idx){ computeProbabilityItem(idx){
return <span className={"waiting"}>Currently Unavailable...</span> return <span className={"waiting"}>Currently Unavailable...</span>
if(this.state.gameInfo.gameStatus !== "ready" && this.state.gameInfo.turn < this.moveHistory.length){ // if(this.state.gameInfo.gameStatus !== "ready" && this.state.gameInfo.turn < this.moveHistory.length){
let style = {}; // let style = {};
style["backgroundColor"] = this.moveHistory[this.state.gameInfo.turn].probabilities.length > idx ? `rgba(63, 81, 181, ${this.moveHistory[this.state.gameInfo.turn].probabilities[idx].probability})` : "#bdbdbd"; // style["backgroundColor"] = this.moveHistory[this.state.gameInfo.turn].probabilities.length > idx ? `rgba(63, 81, 181, ${this.moveHistory[this.state.gameInfo.turn].probabilities[idx].probability})` : "#bdbdbd";
return ( // return (
<div className={"playing"} style={style}> // <div className={"playing"} style={style}>
<div className="probability-move"> // <div className="probability-move">
{this.moveHistory[this.state.gameInfo.turn].probabilities.length > idx ? // {this.moveHistory[this.state.gameInfo.turn].probabilities.length > idx ?
this.computeSingleLineHand(this.cardStr2Arr(this.moveHistory[this.state.gameInfo.turn].probabilities[idx].move)) // this.computeSingleLineHand(this.cardStr2Arr(this.moveHistory[this.state.gameInfo.turn].probabilities[idx].move))
: // :
<NotInterestedIcon fontSize="large" /> // <NotInterestedIcon fontSize="large" />
} // }
</div> // </div>
{this.moveHistory[this.state.gameInfo.turn].probabilities.length > idx ? // {this.moveHistory[this.state.gameInfo.turn].probabilities.length > idx ?
<div className={"non-card"}> // <div className={"non-card"}>
<span>{this.moveHistory[this.state.gameInfo.turn].probabilities.length > idx ? `Probability ${(this.moveHistory[this.state.gameInfo.turn].probabilities[idx].probability * 100).toFixed(2)}%` : ""}</span> // <span>{this.moveHistory[this.state.gameInfo.turn].probabilities.length > idx ? `Probability ${(this.moveHistory[this.state.gameInfo.turn].probabilities[idx].probability * 100).toFixed(2)}%` : ""}</span>
</div> // </div>
: // :
"" // ""
} // }
</div> // </div>
) // )
}else { // }else {
return <span className={"waiting"}>Waiting...</span> // return <span className={"waiting"}>Waiting...</span>
} // }
} }
go2PrevGameState() { go2PrevGameState() {

View File

@ -34,32 +34,55 @@ const modelList = [
]; ];
function LeaderBoard () { function LeaderBoard () {
const initRowsPerPage = 10;
const [rowsPerPage, setRowsPerPage] = React.useState(initRowsPerPage);
const [page, setPage] = React.useState(0);
const [rowsTotal, setRowsTotal] = React.useState(0);
const [rows, setRows] = React.useState([]); const [rows, setRows] = React.useState([]);
const { type, name } = qs.parse(window.location.search); const { type, name } = qs.parse(window.location.search);
let requestUrl = `${apiUrl}/tournament/`; let requestUrl = `${apiUrl}/tournament/`;
if (type === 'game') { if (type === 'game') {
requestUrl += `query_game?name=${name}` requestUrl += `query_agent_payoff?name=${name}&elements_every_page=${rowsPerPage}&page_index=${page}`
} else if (type === 'agent') { } else if (type === 'agent') {
requestUrl += `query_game?agent0=${name}` requestUrl += `query_game?agent0=${name}&elements_every_page=${rowsPerPage}&page_index=${page}`
} }
console.log(requestUrl); console.log(requestUrl);
// todo: detect type change then reset page and page size
useEffect(() => { useEffect(() => {
async function fetchData() { async function fetchData() {
const res = await axios.get(requestUrl); const res = await axios.get(requestUrl);
console.log('wdnmd', res); console.log(res);
setRows(res.data.map((resRow) => {return createData(resRow);})); if (type === 'game') {
setRows(res.data.data.map((resRow, index) => {
const rank = rowsPerPage * page + index + 1;
return createLeaderBoardData(resRow, rank);
}));
} else if (type === 'agent') {
setRows(res.data.data.map((resRow) => {return createData(resRow);}));
}
setRowsTotal(res.data.total_row)
} }
fetchData(); fetchData();
}, [requestUrl]) }, [requestUrl, page, rowsPerPage, type])
return ( return (
<div> <div>
<MenuBar gameList={gameList} modelList={modelList} /> <MenuBar gameList={gameList} modelList={modelList} />
<div style={{marginLeft: '250px'}}> <div style={{marginLeft: '250px'}}>
<div style={{padding: 20}}> <div style={{padding: 20}}>
<EnhancedTable tableRows={rows} routeInfo={{type, name}}/> <EnhancedTable
tableRows={rows}
routeInfo={{type, name}}
page={page}
setPage={(q) => {setPage(q)}}
rowsPerPage={rowsPerPage}
setRowsPerPage={(q) => {setRowsPerPage(q)}}
rowsTotal={rowsTotal}
type={type}
/>
</div> </div>
</div> </div>
</div> </div>
@ -78,7 +101,15 @@ function createData(resData) {
}; };
} }
const headCells = [ function createLeaderBoardData(resData, rank) {
return {
rank: rank,
agent: resData.agent0,
payoff: resData.payoff
}
}
const agentHeadCells = [
{ id: 'id', numeric: false, disablePadding: false, label: 'ID' }, { id: 'id', numeric: false, disablePadding: false, label: 'ID' },
{ id: 'game', numeric: false, disablePadding: false, label: 'Game' }, { id: 'game', numeric: false, disablePadding: false, label: 'Game' },
{ id: 'agent0', numeric: false, disablePadding: false, label: 'Agent 0' }, { id: 'agent0', numeric: false, disablePadding: false, label: 'Agent 0' },
@ -88,6 +119,12 @@ const headCells = [
{ id: 'replay', numeric: false, disablePadding: false, label: 'Replay' } { id: 'replay', numeric: false, disablePadding: false, label: 'Replay' }
]; ];
const leaderBoardHeadCells = [
{ id: 'rank', numeric: false, disablePadding: false, label: 'Rank' },
{ id: 'agent', numeric: false, disablePadding: false, label: 'Agent' },
{ id: 'payoff', numeric: false, disablePadding: false, label: 'Payoff' }
];
const StyledTableCell = withStyles((theme) => ({ const StyledTableCell = withStyles((theme) => ({
head: { head: {
backgroundColor: '#373538', backgroundColor: '#373538',
@ -97,7 +134,8 @@ const StyledTableCell = withStyles((theme) => ({
} }
}))(TableCell); }))(TableCell);
function EnhancedTableHead() { function EnhancedTableHead(props) {
const {headCells} = props;
return ( return (
<TableHead> <TableHead>
<TableRow> <TableRow>
@ -115,11 +153,6 @@ function EnhancedTableHead() {
); );
} }
EnhancedTableHead.propTypes = {
classes: PropTypes.object.isRequired,
rowCount: PropTypes.number.isRequired,
};
const useToolbarStyles = makeStyles((theme) => ({ const useToolbarStyles = makeStyles((theme) => ({
root: { root: {
paddingLeft: theme.spacing(2), paddingLeft: theme.spacing(2),
@ -206,33 +239,11 @@ const useStyles = makeStyles((theme) => ({
}, },
})); }));
const EnhancedTable = (props) => { const LeaderBoardTableContent = (props) => {
const initRowsPerPage = 10; const { tableRows, rowsPerPage, page, rowsTotal, headCells } = props;
const { tableRows, routeInfo } = props;
const classes = useStyles(); const classes = useStyles();
const [rowsPerPage, setRowsPerPage] = React.useState(initRowsPerPage); const emptyRows = rowsPerPage - Math.min(rowsPerPage, rowsTotal - page * rowsPerPage);
const [page, setPage] = React.useState(0);
useEffect(() => {
setRowsPerPage(initRowsPerPage);
setPage(0);
}, [tableRows]);
const handleChangePage = (event, newPage) => {
setPage(newPage);
};
const handleChangeRowsPerPage = (event) => {
setRowsPerPage(parseInt(event.target.value, 10));
setPage(0);
};
const emptyRows = rowsPerPage - Math.min(rowsPerPage, tableRows.length - page * rowsPerPage);
return ( return (
<div className={classes.root}>
<Paper className={classes.paper}>
<EnhancedTableToolbar routeInfo={routeInfo}/>
<TableContainer> <TableContainer>
<Table <Table
className={classes.table} className={classes.table}
@ -240,14 +251,51 @@ const EnhancedTable = (props) => {
size={'medium'} size={'medium'}
aria-label="enhanced table" aria-label="enhanced table"
> >
<EnhancedTableHead <EnhancedTableHead headCells={headCells}/>
classes={classes}
rowCount={tableRows.length}
/>
<TableBody> <TableBody>
{tableRows {tableRows.map((row, index) => {
.slice(page * rowsPerPage, page * rowsPerPage + rowsPerPage) const labelId = `enhanced-table-checkbox-${index}`;
.map((row, index) => { return (
<TableRow
hover
role="checkbox"
tabIndex={-1}
key={row.rank}
>
<TableCell component="th" id={labelId} scope="row">
{row.rank}
</TableCell>
<TableCell>{row.agent}</TableCell>
<TableCell>{row.payoff}</TableCell>
</TableRow>
);
})}
{emptyRows > 0 && (
<TableRow style={{ height: 53 * emptyRows }}>
<TableCell colSpan={3} />
</TableRow>
)}
</TableBody>
</Table>
</TableContainer>
)
}
const AgentTableContent = (props) => {
const { tableRows, rowsPerPage, page, rowsTotal, headCells } = props;
const classes = useStyles();
const emptyRows = rowsPerPage - Math.min(rowsPerPage, rowsTotal - page * rowsPerPage);
return (
<TableContainer>
<Table
className={classes.table}
aria-labelledby="tableTitle"
size={'medium'}
aria-label="enhanced table"
>
<EnhancedTableHead headCells={headCells}/>
<TableBody>
{tableRows.map((row, index) => {
const labelId = `enhanced-table-checkbox-${index}`; const labelId = `enhanced-table-checkbox-${index}`;
return ( return (
<TableRow <TableRow
@ -264,22 +312,52 @@ const EnhancedTable = (props) => {
<TableCell>{row.agent1}</TableCell> <TableCell>{row.agent1}</TableCell>
<TableCell>{row.win}</TableCell> <TableCell>{row.win}</TableCell>
<TableCell>{row.payoff}</TableCell> <TableCell>{row.payoff}</TableCell>
<TableCell><a style={{display: "table-cell"}} href={row.replayUrl} target="_blank"><PlayCircleOutlineIcon style={{verticalAlign: "middle"}}/></a></TableCell> <TableCell><a style={{display: "table-cell"}} href={row.replayUrl} rel="noopener noreferrer" target="_blank"><PlayCircleOutlineIcon style={{verticalAlign: "middle"}}/></a></TableCell>
</TableRow> </TableRow>
); );
})} })}
{emptyRows > 0 && ( {emptyRows > 0 && (
<TableRow style={{ height: 53 * emptyRows }}> <TableRow style={{ height: 53 * emptyRows }}>
<TableCell colSpan={6} /> <TableCell colSpan={7} />
</TableRow> </TableRow>
)} )}
</TableBody> </TableBody>
</Table> </Table>
</TableContainer> </TableContainer>
)
}
const EnhancedTable = (props) => {
const { tableRows, routeInfo, rowsPerPage, page, setPage, setRowsPerPage, rowsTotal, type } = props;
const classes = useStyles();
const handleChangePage = (event, newPage) => {
setPage(newPage);
};
const handleChangeRowsPerPage = (event) => {
setRowsPerPage(parseInt(event.target.value, 10));
setPage(0);
};
let tableContent = '';
if (type === 'game') {
tableContent = <LeaderBoardTableContent headCells={leaderBoardHeadCells} tableRows={tableRows} rowsPerPage={rowsPerPage} page={page} rowsTotal={rowsTotal}/>;
} else if (type === 'agent') {
tableContent = <AgentTableContent headCells={agentHeadCells} tableRows={tableRows} rowsPerPage={rowsPerPage} page={page} rowsTotal={rowsTotal}/>;
}
return (
<div className={classes.root}>
<Paper className={classes.paper}>
<EnhancedTableToolbar routeInfo={routeInfo}/>
{tableContent}
<TablePagination <TablePagination
rowsPerPageOptions={[10, 50, 100]} // todo: remove testing page size option
rowsPerPageOptions={[2, 10, 50, 100]}
component="div" component="div"
count={tableRows.length} count={rowsTotal}
rowsPerPage={rowsPerPage} rowsPerPage={rowsPerPage}
page={page} page={page}
onChangePage={handleChangePage} onChangePage={handleChangePage}