* cli

* add timeout for class Room

* request func, roomowner

* request

* corrent stupid Qml/JS

* chooseGenerals

* prepareForStart(part 1)

* fix require grammar
This commit is contained in:
Notify-ctrl 2022-03-30 14:14:40 +08:00 committed by GitHub
parent 58ea0ca80a
commit 7fd127b849
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
35 changed files with 878 additions and 139 deletions

BIN
image/photo/owner.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 6.2 KiB

BIN
image/photo/ready.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 7.1 KiB

View File

@ -1,7 +1,7 @@
Client = class('Client')
-- load client classes
ClientPlayer = require "client/clientplayer"
ClientPlayer = require "client.clientplayer"
freekill.client_callback = {}

View File

@ -106,8 +106,8 @@ function Engine:getGeneralsRandomly(num, generalPool, except, filter)
except = except or {}
local availableGenerals = {}
for _, general in ipairs(generalPool) do
if not table.contains(except, general) and not (filter and filter(general)) then
for _, general in pairs(generalPool) do
if not table.contains(except, general.name) and not (filter and filter(general)) then
table.insert(availableGenerals, general)
end
end
@ -117,9 +117,9 @@ function Engine:getGeneralsRandomly(num, generalPool, except, filter)
end
local result = {}
while num > 0 do
for i = 1, num do
local randomGeneral = math.random(1, #availableGenerals)
table.insert(result, randomGeneral)
table.insert(result, availableGenerals[randomGeneral])
table.remove(availableGenerals, randomGeneral)
if #availableGenerals == 0 then

View File

@ -13,6 +13,7 @@ function Player:initialize()
self.chained = false
self.dying = false
self.dead = false
self.state = ""
self.playerSkills = {}
end

View File

@ -58,6 +58,8 @@ FileIO = {
isDir = freekill.QmlBackend_isDir
}
os.getms = freekill.GetMicroSecond
Stack = class("Stack")
function Stack:initialize()
self.t = {}

View File

@ -8,7 +8,7 @@ package.path = package.path .. ";./lua/lib/?.lua"
class = require "middleclass"
json = require "json"
require "sha256"
Util = require "core/util"
Util = require "core.util"
math.randomseed(os.time())
DebugMode = true
@ -20,12 +20,12 @@ function pt(t)
end
-- load core classes
Engine = require "core/engine"
Package = require "core/package"
General = require "core/general"
Card = require "core/card"
Skill = require "core/skill"
Player = require "core/player"
Engine = require "core.engine"
Package = require "core.package"
General = require "core.general"
Card = require "core.card"
Skill = require "core.skill"
Player = require "core.player"
-- load packages
Fk = Engine:new()

View File

@ -25,30 +25,106 @@ function GameLogic:run()
self.room:adjustSeats()
self:chooseGenerals()
self:startGame()
self:prepareForStart()
self:action()
end
function GameLogic:assignRoles()
local n = #self.room.players
local room = self.room
local n = #room.players
local roles = self.role_table[n]
table.shuffle(roles)
for i = 1, n do
local p = self.room.players[i]
local p = room.players[i]
p.role = roles[i]
if p.role == "lord" then
self.room:broadcastProperty(p, "role")
room:broadcastProperty(p, "role")
else
self.room:notifyProperty(p, p, "role")
room:notifyProperty(p, p, "role")
end
end
end
function GameLogic:chooseGenerals()
local room = self.room
local function setPlayerGeneral(player, general)
if Fk.generals[general] == nil then return end
player.general = general
self.room:notifyProperty(player, player, "general")
end
local lord = room:getLord()
local lord_general = nil
if lord ~= nil then
local generals = Fk:getGeneralsRandomly(3)
for i = 1, #generals do
generals[i] = generals[i].name
end
lord_general = room:askForGeneral(lord, generals)
setPlayerGeneral(lord, lord_general)
room:broadcastProperty(lord, "general")
end
local nonlord = room:getOtherPlayers(lord)
local generals = Fk:getGeneralsRandomly(#nonlord * 3, Fk.generals, {lord_general})
table.shuffle(generals)
for _, p in ipairs(nonlord) do
local arg = {
(table.remove(generals, 1)).name,
(table.remove(generals, 1)).name,
(table.remove(generals, 1)).name,
}
p.request_data = json.encode(arg)
p.default_reply = arg[1]
end
room:doBroadcastRequest("AskForGeneral", nonlord)
for _, p in ipairs(nonlord) do
if p.general == "" and p.reply_ready then
local general = json.decode(p.client_reply)[1]
setPlayerGeneral(p, general)
else
setPlayerGeneral(p, p.default_reply)
end
p.default_reply = ""
end
end
function GameLogic:startGame()
function GameLogic:prepareForStart()
local room = self.room
local players = room.players
room.alive_players = players
for i = 1, #players - 1 do
players[i].next = players[i + 1]
end
players[#players].next = players[1]
for _, p in ipairs(players) do
assert(p.general ~= "")
local general = Fk.generals[p.general]
p.maxHp = general.maxHp
p.hp = general.hp
-- TODO: setup AI here
if p.role ~= "lord" then
room:broadcastProperty(p, "general")
elseif #players >= 5 then
p.maxHp = p.maxHp + 1
p.hp = p.hp + 1
end
room:broadcastProperty(p, "maxHp")
room:broadcastProperty(p, "hp")
-- TODO: add skills to player
end
-- TODO: prepare drawPile
-- TODO: init cards in drawPile
-- TODO: init trigger table for self
end
function GameLogic:action()
end

View File

@ -3,13 +3,16 @@ local Room = class("Room")
function Room:initialize(_room)
self.room = _room
self.players = {} -- ServerPlayer[]
self.gameFinished = false
self.alive_players = {}
self.game_finished = false
self.timeout = _room:getTimeout()
end
-- When this function returns, the Room(C++) thread stopped.
function Room:run()
for _, p in freekill.qlist(self.room:getPlayers()) do
local player = ServerPlayer:new(p)
player.state = p:getStateString()
table.insert(self.players, player)
self.server.players[player:getId()] = player
end
@ -32,8 +35,55 @@ function Room:notifyProperty(p, player, property)
})
end
function Room:doBroadcastNotify(command, jsonData)
self.room:doBroadcastNotify(self.room:getPlayers(), command, jsonData)
function Room:doBroadcastNotify(command, jsonData, players)
players = players or self.players
local tolist = freekill.SPlayerList()
for _, p in ipairs(players) do
tolist:append(p.serverplayer)
end
self.room:doBroadcastNotify(tolist, command, jsonData)
end
function Room:doRequest(player, command, jsonData, wait)
if wait == nil then wait = true end
player:doRequest(command, jsonData, self.timeout)
if wait then
return player:waitForReply(self.timeout)
end
end
function Room:doBroadcastRequest(command, players)
players = players or self.players
self:notifyMoveFocus(players, command)
for _, p in ipairs(players) do
self:doRequest(p, command, p.request_data, false)
end
local remainTime = self.timeout
local currentTime = os.time()
local elapsed = 0
for _, p in ipairs(players) do
elapsed = os.time() - currentTime
remainTime = remainTime - elapsed
p:waitForReply(remainTime)
end
end
function Room:notifyMoveFocus(players, command)
if (players.class) then
players = {players}
end
local ids = {}
for _, p in ipairs(players) do
table.insert(ids, p:getId())
end
self:doBroadcastNotify("MoveFocus", json.encode{
ids,
command
})
end
function Room:adjustSeats()
@ -64,8 +114,45 @@ function Room:adjustSeats()
self:doBroadcastNotify("ArrangeSeats", json.encode(player_circle))
end
function Room:getLord()
local lord = self.players[1]
if lord.role == "lord" then return lord end
for _, p in ipairs(self.players) do
if p.role == "lord" then return p end
end
return nil
end
function Room:getOtherPlayers(expect)
local ret = {table.unpack(self.players)}
table.removeOne(ret, expect)
return ret
end
function Room:askForGeneral(player, generals)
local command = "AskForGeneral"
self:notifyMoveFocus(player, command)
if #generals == 1 then return generals[1] end
local defaultChoice = generals[1]
if (player.state == "online") then
local result = self:doRequest(player, command, json.encode(generals))
if result == "" then
return defaultChoice
else
-- TODO: result is a JSON array
-- update here when choose multiple generals
return json.decode(result)[1]
end
end
return defaultChoice
end
function Room:gameOver()
self.gameFinished = true
self.game_finished = true
-- dosomething
self.room:gameOver()
end

View File

@ -1,9 +1,9 @@
Server = class('Server')
-- load server classes
Room = require "server/room"
GameLogic = require "server/gamelogic"
ServerPlayer = require "server/serverplayer"
Room = require "server.room"
GameLogic = require "server.gamelogic"
ServerPlayer = require "server.serverplayer"
freekill.server_callback = {}

View File

@ -3,6 +3,14 @@ local ServerPlayer = Player:subclass("ServerPlayer")
function ServerPlayer:initialize(_self)
Player.initialize(self)
self.serverplayer = _self
self.next = nil
-- Below are for doBroadcastRequest
self.request_data = ""
self.client_reply = ""
self.default_reply = ""
self.reply_ready = false
end
function ServerPlayer:getId()
@ -14,7 +22,23 @@ function ServerPlayer:doNotify(command, jsonData)
end
function ServerPlayer:doRequest(command, jsonData, timeout)
timeout = timeout or self.room.timeout
self.client_reply = ""
self.reply_ready = false
self.serverplayer:doRequest(command, jsonData, timeout)
end
function ServerPlayer:waitForReply(timeout)
local result = ""
if timeout == nil then
result = self.serverplayer:waitForReply()
else
result = self.serverplayer:waitForReply(timeout)
end
self.request_data = ""
self.client_reply = result
if result ~= "" then self.reply_ready = true end
return result
end
return ServerPlayer

View File

@ -1,5 +1,5 @@
local extension = Package:new("standard")
extension.metadata = require "standard/metadata"
extension.metadata = require "standard.metadata"
Fk:loadTranslationTable{
["wei"] = "",

View File

@ -9,4 +9,5 @@ QtObject {
// Client data
property int roomCapacity: 0
property int roomTimeout: 0
}

View File

@ -29,7 +29,10 @@ callbacks["EnterLobby"] = function(jsonData) {
}
callbacks["EnterRoom"] = function(jsonData) {
config.roomCapacity = JSON.parse(jsonData)[0];
// jsonData: int capacity, int timeout
let data = JSON.parse(jsonData);
config.roomCapacity = data[0];
config.roomTimeout = data[1];
mainStack.push(room);
mainWindow.busy = false;
}

69
qml/Pages/MetroButton.qml Normal file
View File

@ -0,0 +1,69 @@
import QtQuick 2.15
Item {
property bool enabled: true
property alias text: title.text
property alias textColor: title.color
property alias textFont: title.font
property alias backgroundColor: bg.color
property alias border: bg.border
property alias iconSource: icon.source
property int padding: 5
signal clicked
id: button
width: icon.width + title.implicitWidth + padding * 2
height: Math.max(icon.height, title.implicitHeight) + padding * 2
Rectangle {
id: bg
anchors.fill: parent
color: "black"
border.width: 2
border.color: "white"
opacity: 0.8
}
states: [
State {
name: "hovered"; when: mouse.containsMouse
PropertyChanges { target: bg; color: "white" }
PropertyChanges { target: title; color: "black" }
},
State {
name: "disabled"; when: !enabled
PropertyChanges { target: button; opacity: 0.2 }
}
]
MouseArea {
id: mouse
anchors.fill: parent
hoverEnabled: parent.enabled
onReleased: if (parent.enabled) parent.clicked()
}
Row {
x: padding
y: padding
anchors.centerIn: parent
spacing: 5
Image {
id: icon
anchors.verticalCenter: parent.verticalCenter
fillMode: Image.PreserveAspectFit
}
Text {
id: title
font.pixelSize: 18
// font.family: "WenQuanYi Micro Hei"
anchors.verticalCenter: parent.verticalCenter
text: ""
color: "white"
}
}
}

View File

@ -13,11 +13,9 @@ Item {
property bool isOwner: false
property bool isStarted: false
property alias popupBox: popupBox
// tmp
Text {
anchors.centerIn: parent
text: "You are in room."
}
Button {
text: "quit"
anchors.bottom: parent.bottom
@ -28,7 +26,7 @@ Item {
}
Button {
text: "start game"
visible: isOwner && !isStarted
visible: dashboardModel.isOwner && !isStarted
anchors.centerIn: parent
}
@ -73,20 +71,20 @@ Item {
id: photos
model: photoModel
Photo {
general: _general
screenName: _screenName
role: _role
kingdom: _kingdom
netstate: _netstate
maxHp: _maxHp
hp: _hp
seatNumber: _seatNumber
isDead: _isDead
dying: _dying
faceturned: _faceturned
chained: _chained
drank: _drank
isOwner: _isOwner
general: model.general
screenName: model.screenName
role: model.role
kingdom: model.kingdom
netstate: model.netstate
maxHp: model.maxHp
hp: model.hp
seatNumber: model.seatNumber
isDead: model.isDead
dying: model.dying
faceturned: model.faceturned
chained: model.chained
drank: model.drank
isOwner: model.isOwner
}
}
@ -129,6 +127,30 @@ Item {
self.isOwner: dashboardModel.isOwner
}
Loader {
id: popupBox
onSourceChanged: {
if (item === null)
return;
item.finished.connect(function(){
source = "";
});
item.widthChanged.connect(function(){
popupBox.moveToCenter();
});
item.heightChanged.connect(function(){
popupBox.moveToCenter();
});
moveToCenter();
}
function moveToCenter()
{
item.x = Math.round((roomArea.width - item.width) / 2);
item.y = Math.round(roomArea.height * 0.67 - item.height / 2);
}
}
Component.onCompleted: {
toast.show("Sucesessfully entered room.");
@ -157,20 +179,20 @@ Item {
photoModel.append({
id: -1,
index: i - 1, // For animating seat swap
_general: "",
_screenName: "",
_role: "unknown",
_kingdom: "qun",
_netstate: "online",
_maxHp: 0,
_hp: 0,
_seatNumber: i + 1,
_isDead: false,
_dying: false,
_faceturned: false,
_chained: false,
_drank: false,
_isOwner: false
general: "",
screenName: "",
role: "unknown",
kingdom: "qun",
netstate: "online",
maxHp: 0,
hp: 0,
seatNumber: i + 1,
isDead: false,
dying: false,
faceturned: false,
chained: false,
drank: false,
isOwner: false
});
}

View File

@ -20,9 +20,9 @@ Item {
function remove(outputs)
{
var result = [];
for (var i = 0; i < cards.length; i++) {
for (var j = 0; j < outputs.length; j++) {
let result = [];
for (let i = 0; i < cards.length; i++) {
for (let j = 0; j < outputs.length; j++) {
if (outputs[j] === cards[i].cid) {
result.push(cards[i]);
cards.splice(i, 1);
@ -37,9 +37,9 @@ Item {
function updateCardPosition(animated)
{
var i, card;
let i, card;
var overflow = false;
let overflow = false;
for (i = 0; i < cards.length; i++) {
card = cards[i];
card.origX = i * card.width;
@ -52,8 +52,8 @@ Item {
if (overflow) {
// TODO: Adjust cards in multiple lines if there are too many cards
var xLimit = root.width - card.width;
var spacing = xLimit / (cards.length - 1);
let xLimit = root.width - card.width;
let spacing = xLimit / (cards.length - 1);
for (i = 0; i < cards.length; i++) {
card = cards[i];
card.origX = i * spacing;
@ -61,7 +61,7 @@ Item {
}
}
var parentPos = roomScene.mapFromItem(root, 0, 0);
let parentPos = roomScene.mapFromItem(root, 0, 0);
for (i = 0; i < cards.length; i++) {
card = cards[i];
card.origX += parentPos.x;

View File

@ -26,6 +26,7 @@ Item {
property bool footnoteVisible: true
property bool known: true // if false it only show a card back
property bool enabled: true // if false the card will be grey
property alias card: cardItem
property alias glow: glowItem
function getColor() {
@ -38,9 +39,12 @@ Item {
property int cid: 0
property bool selectable: true
property bool selected: false
property bool draggable: false
property bool autoBack: true
property int origX: 0
property int origY: 0
property real origOpacity: 0
property real origOpacity: 1
property bool isClicked: false
property bool moveAborted: false
property alias goBackAnim: goBackAnimation
property int goBackDuration: 500
@ -71,6 +75,7 @@ Item {
source: known ? (name != "" ? SkinBank.CARD_DIR + name : "")
: (SkinBank.CARD_DIR + "card-back")
anchors.fill: parent
fillMode: Image.PreserveAspectCrop
}
Image {
@ -97,7 +102,7 @@ Item {
Image {
id: colorItem
visible: known && suit == ""
source: visible ? SkinBank.CARD_SUIT_DIR + "/" + color : ""
source: (visible && color != "") ? SkinBank.CARD_SUIT_DIR + "/" + color : ""
x: 1
}
@ -126,6 +131,41 @@ Item {
opacity: 0.7
}
MouseArea {
anchors.fill: parent
drag.target: draggable ? parent : undefined
drag.axis: Drag.XAndYAxis
hoverEnabled: true
onReleased: {
root.isClicked = mouse.isClick;
parent.released();
if (autoBack)
goBackAnimation.start();
}
onEntered: {
parent.entered();
if (draggable) {
glow.visible = true;
root.z++;
}
}
onExited: {
parent.exited();
if (draggable) {
glow.visible = false;
root.z--;
}
}
onClicked: {
selected = selectable ? !selected : false;
parent.clicked();
}
}
ParallelAnimation {
id: goBackAnimation
@ -180,7 +220,7 @@ Item {
function toData()
{
var data = {
let data = {
cid: cid,
name: name,
suit: suit,

View File

@ -0,0 +1,177 @@
import QtQuick 2.15
import ".."
import "../skin-bank.js" as SkinBank
GraphicsBox {
property alias generalList: generalList
// property var generalList: []
property int choiceNum: 1
property var choices: []
property var selectedItem: []
property bool loaded: false
ListModel {
id: generalList
}
id: root
title.text: qsTr("Please choose ") + choiceNum + qsTr(" general(s)")
width: generalArea.width + body.anchors.leftMargin + body.anchors.rightMargin
height: body.implicitHeight + body.anchors.topMargin + body.anchors.bottomMargin
Column {
id: body
anchors.fill: parent
anchors.margins: 40
anchors.bottomMargin: 20
Item {
id: generalArea
width: (generalList.count >= 5 ? Math.ceil(generalList.count / 2) : Math.max(3, generalList.count)) * 97
height: generalList.count >= 5 ? 290 : 150
z: 1
Repeater {
id: generalMagnetList
model: generalList.count
Item {
width: 93
height: 130
x: (index % Math.ceil(generalList.count / (generalList.count >= 5 ? 2 : 1))) * 98 + (generalList.count >= 5 && index > generalList.count / 2 && generalList.count % 2 == 1 ? 50 : 0)
y: generalList.count < 5 ? 0 : (index < generalList.count / 2 ? 0 : 135)
}
}
}
Item {
id: splitLine
width: parent.width - 80
height: 6
anchors.horizontalCenter: parent.horizontalCenter
clip: true
}
Item {
width: parent.width
height: 165
Row {
id: resultArea
anchors.centerIn: parent
spacing: 10
Repeater {
id: resultList
model: choiceNum
Rectangle {
color: "#1D1E19"
radius: 3
width: 93
height: 130
}
}
}
}
Item {
id: buttonArea
width: parent.width
height: 40
MetroButton {
id: fightButton
anchors.horizontalCenter: parent.horizontalCenter
anchors.bottom: parent.bottom
text: qsTr("Fight")
width: 120
height: 35
enabled: false
onClicked: close();
}
}
}
Repeater {
id: generalCardList
model: generalList
GeneralCardItem {
name: model.name
selectable: true
draggable: true
onClicked: {
let toSelect = true;
for (let i = 0; i < selectedItem.length; i++) {
if (selectedItem[i] === this) {
toSelect = false;
selectedItem.splice(i, 1);
}
}
if (toSelect && selectedItem.length < choiceNum)
selectedItem.push(this);
updatePosition();
}
onReleased: {
if (!isClicked)
arrangeCards();
}
}
}
function arrangeCards()
{
let item, i;
selectedItem = [];
for (i = 0; i < generalList.count; i++) {
item = generalCardList.itemAt(i);
if (item.y > splitLine.y)
selectedItem.push(item);
}
selectedItem.sort((a, b) => a.x - b.x);
if (selectedItem.length > choiceNum)
selectedItem.splice(choiceNum, selectedItem.length - choiceNum);
updatePosition();
}
function updatePosition()
{
choices = [];
let item, magnet, pos, i;
for (i = 0; i < selectedItem.length && i < resultList.count; i++) {
item = selectedItem[i];
choices.push(item.name);
magnet = resultList.itemAt(i);
pos = root.mapFromItem(resultArea, magnet.x, magnet.y);
if (item.origX !== pos.x || item.origY !== item.y) {
item.origX = pos.x;
item.origY = pos.y;
item.goBack(true);
}
}
fightButton.enabled = (choices.length == choiceNum);
for (i = 0; i < generalCardList.count; i++) {
item = generalCardList.itemAt(i);
if (selectedItem.indexOf(item) != -1)
continue;
magnet = generalMagnetList.itemAt(i);
pos = root.mapFromItem(generalMagnetList.parent, magnet.x, magnet.y);
if (item.origX !== pos.x || item.origY !== item.y) {
item.origX = pos.x;
item.origY = pos.y;
item.goBack(true);
}
}
}
}

View File

@ -0,0 +1,24 @@
import QtQuick 2.15
import "../skin-bank.js" as SkinBank
/* Layout of general card:
* +--------+
*kindom|wu 9999| <- hp
*name -|s |
* |q img |
* | |
* | |
* +--------+
* Inherit from CardItem to use common signal
*/
CardItem {
property string kingdom: "qun"
name: "caocao"
// description: Sanguosha.getGeneralDescription(name)
suit: ""
number: 0
footnote: ""
card.source: SkinBank.GENERAL_DIR + name
glow.color: "white" //Engine.kingdomColor[kingdom]
}

View File

@ -0,0 +1,53 @@
import QtQuick 2.15
import QtGraphicalEffects 1.0
Item {
property alias title: titleItem
signal accepted() //Read result
signal finished() //Close the box
id: root
Rectangle {
id: background
anchors.fill: parent
color: "#B0000000"
radius: 5
border.color: "#A6967A"
border.width: 1
}
DropShadow {
source: background
anchors.fill: background
color: "#B0000000"
radius: 5
samples: 12
spread: 0.2
horizontalOffset: 5
verticalOffset: 4
transparentBorder: true
}
Text {
id: titleItem
color: "#E4D5A0"
font.pixelSize: 18
horizontalAlignment: Text.AlignHCenter
anchors.top: parent.top
anchors.topMargin: 4
anchors.horizontalCenter: parent.horizontalCenter
}
MouseArea {
anchors.fill: parent
drag.target: parent
drag.axis: Drag.XAndYAxis
}
function close()
{
accepted();
finished();
}
}

View File

@ -19,7 +19,7 @@ Item {
{
cardArea.add(inputs);
if (inputs instanceof Array) {
for (var i = 0; i < inputs.length; i++)
for (let i = 0; i < inputs.length; i++)
filterInputCard(inputs[i]);
} else {
filterInputCard(inputs);
@ -36,9 +36,9 @@ Item {
function remove(outputs)
{
var result = cardArea.remove(outputs);
var card;
for (var i = 0; i < result.length; i++) {
let result = cardArea.remove(outputs);
let card;
for (let i = 0; i < result.length; i++) {
card = result[i];
card.draggable = false;
card.selectable = false;
@ -49,7 +49,7 @@ Item {
function enableCards(cardIds)
{
var card, i;
let card, i;
for (i = 0; i < cards.length; i++) {
card = cards[i];
card.selectable = cardIds.contains(card.cid);
@ -64,7 +64,7 @@ Item {
{
cardArea.updateCardPosition(false);
var i, card;
let i, card;
for (i = 0; i < cards.length; i++) {
card = cards[i];
if (card.selected)
@ -81,8 +81,8 @@ Item {
{
area.updateCardPosition(true);
for (var i = 0; i < cards.length; i++) {
var card = cards[i];
for (let i = 0; i < cards.length; i++) {
let card = cards[i];
if (card.selected) {
if (!selectedCards.contains(card))
selectCard(card);
@ -101,7 +101,7 @@ Item {
function unselectCard(card)
{
for (var i = 0; i < selectedCards.length; i++) {
for (let i = 0; i < selectedCards.length; i++) {
if (selectedCards[i] === card) {
selectedCards.splice(i, 1);
cardSelected(card.cid, false);
@ -112,7 +112,7 @@ Item {
function unselectAll(exceptId) {
let card = undefined;
for (var i = 0; i < selectedCards.length; i++) {
for (let i = 0; i < selectedCards.length; i++) {
if (selectedCards[i].cid !== exceptId) {
selectedCards[i].selected = false;
} else {

View File

@ -10,9 +10,9 @@ Item {
function add(inputs)
{
var card;
let card;
if (inputs instanceof Array) {
for (var i = 0; i < inputs.length; i++) {
for (let i = 0; i < inputs.length; i++) {
card = inputs[i];
pendingInput.push(card);
cards.push(card.toData());
@ -38,7 +38,7 @@ Item {
if (!checkExisting)
return true;
for (var i = 0; i < cards.length; i++)
for (let i = 0; i < cards.length; i++)
{
if (cards[i].cid === cid)
return true;
@ -48,13 +48,13 @@ Item {
function remove(outputs)
{
var component = Qt.createComponent("CardItem.qml");
let component = Qt.createComponent("CardItem.qml");
if (component.status !== Component.Ready)
return [];
var parentPos = roomScene.mapFromItem(root, 0, 0);
var card;
var items = [];
let parentPos = roomScene.mapFromItem(root, 0, 0);
let card;
let items = [];
for (let i = 0; i < outputs.length; i++) {
if (_contains(outputs[i])) {
let state = JSON.parse(Sanguosha.getCard4Qml(outputs[i]))
@ -82,10 +82,10 @@ Item {
function updateCardPosition(animated)
{
var i, card;
let i, card;
if (animated) {
var parentPos = roomScene.mapFromItem(root, 0, 0);
let parentPos = roomScene.mapFromItem(root, 0, 0);
for (i = 0; i < pendingInput.length; i++) {
card = pendingInput[i];
card.origX = parentPos.x - card.width / 2 + ((i - pendingInput.length / 2) * 15);

View File

@ -1,5 +1,6 @@
import QtQuick 2.15
import QtGraphicalEffects 1.15
import QtQuick.Controls 2.15
import "PhotoElement"
import "../skin-bank.js" as SkinBank
@ -24,6 +25,9 @@ Item {
property bool drank: false
property bool isOwner: false
property alias progressBar: progressBar
property alias progressTip: progressTip.text
Behavior on x {
NumberAnimation { duration: 600; easing.type: Easing.InOutQuad }
}
@ -95,6 +99,15 @@ Item {
visible: root.isDead
}
Image {
anchors.bottom: parent.bottom
anchors.right: parent.right
anchors.bottomMargin: 8
anchors.rightMargin: 4
source: SkinBank.PHOTO_DIR + (isOwner ? "owner" : "ready")
visible: screenName != "" && !roomScene.isStarted
}
Image {
id: turnedOver
visible: root.faceturned
@ -155,6 +168,7 @@ Item {
GlowText {
id: seatNum
visible: !progressBar.visible
anchors.horizontalCenter: parent.horizontalCenter
anchors.bottom: parent.bottom
anchors.bottomMargin: -32
@ -191,4 +205,42 @@ Item {
function tremble() {
trembleAnimation.start()
}
ProgressBar {
id: progressBar
width: parent.width
height: 4
anchors.bottom: parent.bottom
anchors.bottomMargin: -4
from: 0.0
to: 100.0
visible: false
NumberAnimation on value {
running: progressBar.visible
from: 100.0
to: 0.0
duration: config.roomTimeout * 1000
onFinished: {
progressBar.visible = false;
root.progressTip = "";
}
}
}
Image {
anchors.top: progressBar.bottom
anchors.topMargin: 1
source: SkinBank.PHOTO_DIR + "control/tip"
visible: progressTip.text != ""
Text {
id: progressTip
font.family: "FZLiBian-S02"
font.pixelSize: 18
x: 18
color: "white"
text: ""
}
}
}

View File

@ -22,7 +22,7 @@ Item {
running: true
triggeredOnStart: true
onTriggered: {
var i, card;
let i, card;
if (toVanish) {
for (i = 0; i < discardedCards.length; i++) {
card = discardedCards[i];
@ -60,13 +60,13 @@ Item {
function remove(outputs)
{
var i, j;
let i, j;
var result = area.remove(outputs);
var vanished = [];
let result = area.remove(outputs);
let vanished = [];
if (result.length < outputs.length) {
for (i = 0; i < outputs.length; i++) {
var exists = false;
let exists = false;
for (j = 0; j < result.length; j++) {
if (result[j].cid === outputs[i]) {
exists = true;
@ -96,9 +96,9 @@ Item {
if (cards.length <= 0)
return;
var i, card;
let i, card;
var overflow = false;
let overflow = false;
for (i = 0; i < cards.length; i++) {
card = cards[i];
card.homeX = i * card.width;
@ -111,8 +111,8 @@ Item {
if (overflow) {
//@to-do: Adjust cards in multiple lines if there are too many cards
var xLimit = root.width - card.width;
var spacing = xLimit / (cards.length - 1);
let xLimit = root.width - card.width;
let spacing = xLimit / (cards.length - 1);
for (i = 0; i < cards.length; i++) {
card = cards[i];
card.homeX = i * spacing;
@ -120,8 +120,8 @@ Item {
}
}
var offsetX = Math.max(0, (root.width - cards.length * card.width) / 2);
var parentPos = roomScene.mapFromItem(root, 0, 0);
let offsetX = Math.max(0, (root.width - cards.length * card.width) / 2);
let parentPos = roomScene.mapFromItem(root, 0, 0);
for (i = 0; i < cards.length; i++) {
card = cards[i];
card.homeX += parentPos.x + offsetX;

View File

@ -7,14 +7,14 @@ function arrangePhotos() {
* +---------------+
*/
var photoWidth = 175;
var roomAreaPadding = 10;
var verticalPadding = Math.max(10, roomArea.width * 0.01);
var horizontalSpacing = Math.max(30, roomArea.height * 0.1);
var verticalSpacing = (roomArea.width - photoWidth * 7 - verticalPadding * 2) / 6;
const photoWidth = 175;
const roomAreaPadding = 10;
let verticalPadding = Math.max(10, roomArea.width * 0.01);
let horizontalSpacing = Math.max(30, roomArea.height * 0.1);
let verticalSpacing = (roomArea.width - photoWidth * 7 - verticalPadding * 2) / 6;
// Position 1-7
var regions = [
const regions = [
{ x: verticalPadding + (photoWidth + verticalSpacing) * 6, y: roomAreaPadding + horizontalSpacing * 2 },
{ x: verticalPadding + (photoWidth + verticalSpacing) * 5, y: roomAreaPadding + horizontalSpacing },
{ x: verticalPadding + (photoWidth + verticalSpacing) * 4, y: roomAreaPadding },
@ -24,7 +24,7 @@ function arrangePhotos() {
{ x: verticalPadding, y: roomAreaPadding + horizontalSpacing * 2 },
];
var regularSeatIndex = [
const regularSeatIndex = [
[4],
[3, 5],
[1, 4, 7],
@ -33,9 +33,9 @@ function arrangePhotos() {
[1, 2, 3, 5, 6, 7],
[1, 2, 3, 4, 5, 6, 7],
];
var seatIndex = regularSeatIndex[playerNum - 2];
let seatIndex = regularSeatIndex[playerNum - 2];
var item, region, i;
let item, region, i;
for (i = 0; i < playerNum - 1; i++) {
item = photos.itemAt(i);
@ -58,9 +58,8 @@ callbacks["AddPlayer"] = function(jsonData) {
let name = data[1];
let avatar = data[2];
item.id = uid;
item._screenName = name;
item._general = avatar;
photos.itemAt(i).tremble();
item.screenName = name;
item.general = avatar;
return;
}
}
@ -73,19 +72,31 @@ callbacks["RemovePlayer"] = function(jsonData) {
let item = photoModel.get(i);
if (item.id === uid) {
item.id = -1;
item._screenName = "";
item._general = "";
item.screenName = "";
item.general = "";
return;
}
}
}
/*
callbacks["RoomOwner"] = function(jsonData) {
// jsonData: int uid of the owner
toast.show(J)
let uid = JSON.parse(jsonData)[0];
if (dashboardModel.id === uid) {
dashboardModel.isOwner = true;
roomScene.dashboardModelChanged();
return;
}
for (let i = 0; i < photoModel.count; i++) {
let item = photoModel.get(i);
if (item.id === uid) {
item.isOwner = true;
return;
}
}
}
*/
callbacks["PropertyUpdate"] = function(jsonData) {
// jsonData: int id, string property_name, value
@ -103,7 +114,7 @@ callbacks["PropertyUpdate"] = function(jsonData) {
for (let i = 0; i < photoModel.count; i++) {
let item = photoModel.get(i);
if (item.id === uid) {
item["_" + property_name] = value;
item[property_name] = value;
return;
}
}
@ -112,10 +123,11 @@ callbacks["PropertyUpdate"] = function(jsonData) {
callbacks["ArrangeSeats"] = function(jsonData) {
// jsonData: seat order
let order = JSON.parse(jsonData);
roomScene.isStarted = true;
for (let i = 0; i < photoModel.count; i++) {
let item = photoModel.get(i);
item._seatNumber = order.indexOf(item.id) + 1;
item.seatNumber = order.indexOf(item.id) + 1;
}
dashboardModel.seatNumber = order.indexOf(Self.id) + 1;
@ -134,3 +146,45 @@ callbacks["ArrangeSeats"] = function(jsonData) {
arrangePhotos();
}
function cancelAllFocus() {
let item;
for (let i = 0; i < playerNum - 1; i++) {
item = photos.itemAt(i);
item.progressBar.visible = false;
item.progressTip = "";
}
}
callbacks["MoveFocus"] = function(jsonData) {
// jsonData: int[] focuses, string command
cancelAllFocus();
let data = JSON.parse(jsonData);
let focuses = data[0];
let command = data[1];
let item, model;
for (let i = 0; i < playerNum - 1; i++) {
model = photoModel.get(i);
if (focuses.indexOf(model.id) != -1) {
item = photos.itemAt(i);
item.progressBar.visible = true;
item.progressTip = command + " thinking...";
}
}
}
callbacks["AskForGeneral"] = function(jsonData) {
// jsonData: string[] Generals
// TODO: choose multiple generals
let data = JSON.parse(jsonData);
roomScene.popupBox.source = "RoomElement/ChooseGeneralBox.qml";
let box = roomScene.popupBox.item;
box.choiceNum = 1;
box.accepted.connect(() => {
ClientInstance.replyToServer("AskForGeneral", JSON.stringify([box.choices[0]]));
});
for (let i = 0; i < data.length; i++)
box.generalList.append({ "name": data[i] });
box.updatePosition();
}

View File

@ -3,32 +3,39 @@
int main(int argc, char *argv[])
{
QGuiApplication app(argc, argv);
QGuiApplication::setApplicationName("FreeKill");
QGuiApplication::setApplicationVersion("Alpha 0.0.1");
QCoreApplication *app;
QCoreApplication::setApplicationName("FreeKill");
QCoreApplication::setApplicationVersion("Alpha 0.0.1");
QCommandLineParser parser;
parser.setApplicationDescription("FreeKill server");
parser.addHelpOption();
parser.addVersionOption();
parser.addOption({{"s", "server"}, "start server at <port>", "port"});
parser.process(app);
QStringList cliOptions;
for (int i = 0; i < argc; i++)
cliOptions << argv[i];
parser.parse(cliOptions);
bool startServer = parser.isSet("server");
ushort serverPort = 9527;
if (startServer) {
app = new QCoreApplication(argc, argv);
bool ok = false;
if (parser.value("server").toInt(&ok) && ok)
serverPort = parser.value("server").toInt();
Server *server = new Server;
if (!server->listen(QHostAddress::Any, serverPort)) {
fprintf(stderr, "cannot listen on port %d!\n", serverPort);
app.exit(1);
app->exit(1);
}
return app.exec();
return app->exec();
}
app = new QGuiApplication(argc, argv);
QQmlApplicationEngine *engine = new QQmlApplicationEngine;
QmlBackend backend;
@ -44,7 +51,7 @@ int main(int argc, char *argv[])
engine->rootContext()->setContextProperty("Debugging", debugging);
engine->load("qml/main.qml");
int ret = app.exec();
int ret = app->exec();
// delete the engine first
// to avoid "TypeError: Cannot read property 'xxx' of null"

View File

@ -60,7 +60,7 @@ void Router::request(int type, const QString& command,
replyMutex.lock();
expectedReplyId = requestId;
replyTimeout = 0;
replyTimeout = timeout;
requestStartTime = QDateTime::currentDateTime();
m_reply = QString();
replyMutex.unlock();
@ -123,7 +123,7 @@ QString Router::waitForReply()
QString Router::waitForReply(int timeout)
{
replyReadySemaphore.tryAcquire(1, timeout);
replyReadySemaphore.tryAcquire(1, timeout * 1000);
return m_reply;
}
@ -188,7 +188,6 @@ void Router::handlePacket(const QByteArray& rawPacket)
m_reply = jsonData;
// TODO: callback?
qDebug() << rawPacket << Qt::endl;
replyReadySemaphore.release();
if (extraReplyReadySemaphore) {

View File

@ -8,7 +8,9 @@ Room::Room(Server* server)
server->nextRoomId++;
this->server = server;
setParent(server);
owner = nullptr;
gameStarted = false;
timeout = 15;
if (!isLobby()) {
connect(this, &Room::playerAdded, server->lobby(), &Room::removePlayer);
connect(this, &Room::playerRemoved, server->lobby(), &Room::addPlayer);
@ -76,7 +78,7 @@ void Room::setOwner(ServerPlayer *owner)
this->owner = owner;
QJsonArray jsonData;
jsonData << owner->getId();
owner->doNotify("RoomOwner", QJsonDocument(jsonData).toJson());
doBroadcastNotify(players, "RoomOwner", QJsonDocument(jsonData).toJson());
}
void Room::addPlayer(ServerPlayer *player)
@ -101,6 +103,7 @@ void Room::addPlayer(ServerPlayer *player)
// Second, let the player enter room and add other players
jsonData = QJsonArray();
jsonData << this->capacity;
jsonData << this->timeout;
player->doNotify("EnterRoom", QJsonDocument(jsonData).toJson());
foreach (ServerPlayer *p, getOtherPlayers(player)) {
@ -111,6 +114,12 @@ void Room::addPlayer(ServerPlayer *player)
player->doNotify("AddPlayer", QJsonDocument(jsonData).toJson());
}
if (this->owner != nullptr) {
jsonData = QJsonArray();
jsonData << this->owner->getId();
player->doNotify("RoomOwner", QJsonDocument(jsonData).toJson());
}
if (isFull())
start();
}
@ -150,11 +159,17 @@ QList<ServerPlayer *> Room::getOtherPlayers(ServerPlayer* expect) const
ServerPlayer *Room::findPlayer(int id) const
{
foreach (ServerPlayer *p, players) {
if (p->getId() == id)
return p;
}
return nullptr;
return server->findPlayer(id);
}
int Room::getTimeout() const
{
return timeout;
}
void Room::setTimeout(int timeout)
{
this->timeout = timeout;
}
bool Room::isStarted() const

View File

@ -31,6 +31,9 @@ public:
QList<ServerPlayer *> getOtherPlayers(ServerPlayer *expect) const;
ServerPlayer *findPlayer(int id) const;
int getTimeout() const;
void setTimeout(int timeout);
bool isStarted() const;
// ====================================}
@ -64,6 +67,8 @@ private:
ServerPlayer *owner; // who created this room?
QList<ServerPlayer *> players;
bool gameStarted;
int timeout;
};
#endif // _ROOM_H

View File

@ -8,7 +8,7 @@ ServerPlayer::ServerPlayer(Room *room)
{
socket = nullptr;
router = new Router(this, socket, Router::TYPE_SERVER);
setState(Player::Online);
this->room = room;
server = room->getServer();
}
@ -68,6 +68,16 @@ void ServerPlayer::doRequest(const QString& command, const QString& jsonData, in
router->request(type, command, jsonData, timeout);
}
QString ServerPlayer::waitForReply()
{
return router->waitForReply();
}
QString ServerPlayer::waitForReply(int timeout)
{
return router->waitForReply(timeout);
}
void ServerPlayer::doNotify(const QString& command, const QString& jsonData)
{
int type = Router::TYPE_NOTIFICATION | Router::SRC_SERVER | Router::DEST_CLIENT;

View File

@ -24,6 +24,8 @@ public:
void doRequest(const QString &command,
const QString &jsonData, int timeout = -1);
QString waitForReply(int timeout);
QString waitForReply();
void doNotify(const QString &command, const QString &jsonData);
void prepareForRequest(const QString &command,

View File

@ -46,7 +46,9 @@ public:
void speak(const QString &message);
void doRequest(const QString &command,
const QString &json_data, int timeout = -1);
const QString &json_data, int timeout);
QString waitForReply();
QString waitForReply(int timeout);
void doNotify(const QString &command, const QString &json_data);
void prepareForRequest(const QString &command, const QString &data);

View File

@ -34,3 +34,15 @@ public:
%template(PlayerList) QList<const Player *>;
%template(IntList) QList<int>;
%template(BoolList) QList<bool>;
%native(GetMicroSecond) int GetMicroSecond(lua_State *L);
%{
#include <sys/time.h>
static int GetMicroSecond(lua_State *L) {
struct timeval tv;
gettimeofday(&tv, nullptr);
long microsecond = tv.tv_sec * 1000000 + tv.tv_usec;
lua_pushnumber(L, microsecond);
return 1;
}
%}

View File

@ -71,6 +71,8 @@ public:
QList<ServerPlayer *> getPlayers() const;
ServerPlayer *findPlayer(int id) const;
int getTimeout() const;
bool isStarted() const;
// ====================================}