Dominion/static/game.html

604 lines
17 KiB
HTML

<!doctype html>
<html>
<head>
<meta charset="utf-8">
<title>DnD</title>
<link rel="stylesheet" href="/static/main.css">
<style type="text/css">
.pile-counter {
position: absolute;
bottom: 5px;
right: 5px;
color: wheat;
background-color: darkred;
border-radius: 5px;
height: 24px;
width: 35px;
text-align: center;
font-size: 22px;
font-family: sans-serif;
line-height: 24px;
}
.player-area {
display: grid;
grid-template-columns: 100px auto 90px;
grid-template-rows: auto auto;
width: 100%;
height: 145px;
position: relative;
box-sizing: border-box;
grid-gap: 10px;
}
.player-area .buttons {
grid-column-start: 3;
text-align: center;
}
.card {
position: relative;
width: 90px;
margin: 1px;
height: 144px;
}
.player-hand {
border: 1px solid red;
height: 100%;
margin: 0;
box-sizing: border-box;
top: 0;
}
.player-hand img.card:hover {
box-shadow: 0 0 5px blue;
}
.chat-window {
border: 1px solid black;
width: 300px;
position: absolute;
top: 0;
right: 0;
z-index: 100;
}
.supply-area {
position: relative;
padding-left: 30px;
padding-right: 300px;
background-color: dimgray;
box-sizing: border-box;
margin: 5px 0;
display: flex;
flex-flow: wrap;
}
.supply-area h3 {
position: relative;
top: 50px;
left: 0;
transform-origin: 0 0;
transform: rotate(90deg);
padding: 0;
margin: 0;
width: 0;
}
.draw-pile {
width: 90px;
position: relative;
height: 145px;
}
.opponent-hand {
width: 90px;
position: relative;
height: 145px;
}
.setup-screen {
}
.hidden {
display: none;
}
.discard-pile {
width: 90px;
border: 1px dotted green;
height: 144px;
box-sizing: border-box;
position: relative;
}
.discard-pile img.card {
margin-top: -1px;
margin-left: -1px;
}
.discard-pile::after {
color: dimgray;
content: "Discard Pile";
font-size: 14px;
position: absolute;
top: 50%;
left: 50%;
transform: translate(-50%, -50%);
white-space: nowrap;
z-index: -1;
}
.inplay-area {
width: 100%;
height: 180px;
position: relative;
box-sizing: border-box;
transition: height 0.4s ease;
background-color: dimgray;
box-sizing: border-box;
margin: 5px 0;
}
.inplay-area.inactive {
height: 0;
transition: height 0.4s ease;
overflow: hidden;
}
.turn-status {
line-height: 24px;
height: 24px;
position: absolute;
top: 0;
left: 50%;
background-color: rgb(255 255 255 / 34%);
border-radius: 0 0 20px 20px;
padding: 0 20px 0;
transform: translate(-50%, 0);
opacity: 1;
transition: opacity 0.4s ease;
}
.inplay-area.inactive .turn-status {
opacity: 0;
transition: opacity 0.4s ease;
}
.opponent-area {
display: flex;
}
.opponent-status {
grid-template-rows: 30px auto;
grid-template-columns: auto auto auto;
box-sizing: border-box;
margin: 0 5px;
border: 1px solid saddlebrown;
display: grid;
grid-gap: 2px;
padding: 2px;
}
.opponent-status.active {
box-shadow: 0 0 10px red;
}
.opponent-status .name {
grid-column-start: 1;
grid-column-end: span 3;
text-align: center;
grid-row-start: 1;
grid-row-end: 1;
}
.opponent-status .discard-pile {
grid-column-start: 1;
grid-column-end: 1;
grid-row-start: 2;
grid-row-end: 2;
}
.opponent-status .draw-pile {
grid-column-start: 2;
grid-column-end: 2;
grid-row-start: 2;
grid-row-end: 2;
}
.opponent-status .opponent-hand {
grid-column-start: 3;
grid-column-end: 3;
grid-row-start: 2;
grid-row-end: 2;
}
.game-screen {
padding: 10px;
}
</style>
<script src="/static/mithril.js"></script>
</head>
<body>
<div id="game"></div>
<script>
function Chat(initialVnode) {
var keyup = function(e) {
if (e.key == "Enter" && e.srcElement.value.length > 0) {
let msg = { type: "Chat", message: e.srcElement.value };
initialVnode.attrs.socket.send(JSON.stringify(msg));
e.srcElement.value = "";
}
}
return {
view: function(vnode) {
return m(".chat-window",
m("h4", "Players"),
m("ul", vnode.attrs.players.map(function(p) {
return m("li", p.name);
})),
m("h4", "Messages"),
m("ul", {id: "chat"}),
m("input", {
id: "chat_input",
placeholder: "Type to chat...",
onkeyup: keyup
})
)
}
}
}
function SupplyPile(initialVnode) {
return {
view: function(vnode) {
return m(".card",
m("img", {class: "card", src: "/static/images/cards/" + vnode.attrs.name.toLowerCase() + ".jpg"}),
m("span", {class: "pile-counter" }, vnode.attrs.count)
)
}
}
}
function SupplyArea(initialVnode) {
return {
view: function(vnode) {
return m(".supply-area",
m("h3", "Supply"),
vnode.attrs.supply.map(function(pile) {
return m(SupplyPile, pile)
})
)
}
}
}
function DiscardPile(initialVnode) {
return {
view: function(vnode) {
var c;
if (vnode.attrs.card) {
c = m("img", {class: "card", src: "/static/images/cards/" + vnode.attrs.card.toLowerCase() + ".jpg"});
}
return m(".discard-pile", c)
}
}
}
function DrawPile(initialVnode) {
return {
view: function(vnode) {
return m(".draw-pile",
m("img", {class: "card", src: "/static/images/cards/Card_back.jpg"}),
m("span", {class: "pile-counter" }, vnode.attrs.count)
)
}
}
}
function PlayerHand(initialVnode) {
return {
view: function(vnode) {
return m(".player-hand", "Player Hand",
vnode.attrs.hand.map(function(card) {
return m("img", {class: "card", src: "/static/images/cards/" + card.toLowerCase() + ".jpg"})
})
)
}
}
}
function OpponentHand(initialVnode) {
return {
view: function(vnode) {
return m(".opponent-hand",
m("img", {class: "card", src: "/static/images/cards/Card_back.jpg"}),
m("span", {class: "pile-counter" }, vnode.attrs.count)
)
}
}
}
function InPlayArea(initialVnode) {
return {
view: function(vnode) {
var active = vnode.attrs.active ? "" : "inactive";
return m(".inplay-area", {class: active},
"Player play area",
m(".turn-status",
"Actions: " + vnode.attrs.actions + " | Buys: " + vnode.attrs.buys + " | ",
m("img", {src: "/static/images/64px-Coin.png", style: "height: 16px;"}),
": " + vnode.attrs.coin
)
)
}
}
}
function PlayerArea(initialVnode) {
var end_turn_click = function(e) {
var msg = { type: "EndTurn" };
webSocket.send(JSON.stringify(msg));
}
return {
view: function(vnode) {
var local_player = vnode.attrs.players[my_player_id];
return m(".player-area",
m(DrawPile, {count: local_player.draw_pile_count}),
m(PlayerHand, vnode.attrs.player),
m(DiscardPile, {card: local_player.discard_pile}),
m(".buttons",
m("button", {onclick: end_turn_click}, "Pass turn")
)
)
}
}
}
function OpponentStatus(initialVnode) {
return {
view: function(vnode) {
var active = vnode.attrs.id == game_state.active_player ? "active" : "";
return m(".opponent-status", {class: active },
m(".name", vnode.attrs.name),
m(DiscardPile, {card: vnode.attrs.discard_pile}),
m(DrawPile, {count: vnode.attrs.draw_pile_count}),
m(OpponentHand, {count: vnode.attrs.hand_count })
)
}
}
}
function OpponentArea(initialVnode) {
return {
view: function(vnode) {
return m(".opponent-area",
vnode.attrs.players
.filter((p, i) => i != my_player_id)
.map(function(o) {
return m(OpponentStatus, o)
})
)
}
}
}
function GameScreen(initialVnode) {
return {
view: function(vnode) {
var cls = vnode.attrs.active ? "" : "hidden";
return m(".game-screen", {class: cls},
m(OpponentArea, vnode.attrs),
m(InPlayArea, vnode.attrs.opponent_turn_state),
m(SupplyArea, vnode.attrs),
m(InPlayArea, vnode.attrs.player_turn_state),
m(PlayerArea, vnode.attrs)
)
}
}
}
function SetupScreen(initialVnode) {
var start_click = function(e) {
let msg = { type: "StartGame" };
initialVnode.attrs.socket.send(JSON.stringify(msg));
}
return {
view: function(vnode) {
var cls = vnode.attrs.active ? "" : "hidden";
return m(".setup-screen", {class: cls},
m("h3", "Setup"),
m("h4", "Basic cards"),
m(".basic-cards",
vnode.attrs.basic_cards.map(function(card) {
return m("img", {class: "card", src: "/static/images/cards/" + card.toLowerCase() + ".jpg"})
})
),
m("h4", "Kingdom Cards"),
m(".kingdom-cards",
vnode.attrs.kingdom_cards.map(function(card) {
return m("img", {class: "card", src: "/static/images/cards/" + card.toLowerCase() + ".jpg"})
})
),
m("h4", "Starting Deck"),
m(".start-deck",
vnode.attrs.starting_deck.map(function(card) {
return m("img", {class: "card", src: "/static/images/cards/" + card.toLowerCase() + ".jpg"})
})
),
m("button", {onclick: start_click}, "Start Game")
)
}
}
}
var game_state;
var setup_state;
var my_player_id = 0;
var webSocket;
function App(initialVnode) {
if (document.location.protocol == "https:") {
url = document.location.href.replace("https://", "wss://") + "/ws";
} else {
url = document.location.href.replace("http://", "ws://") + "/ws";
}
webSocket = new WebSocket(url);
setup_state = {
starting_deck: [],
basic_cards: ["Copper", "Silver", "Gold", "Estate", "Duchery", "Province", "Curse"],
kingdom_cards: ["Cellar", "Moat", "Village", "Merchant", "Workshop", "Smithy", "Remodel", "Militia", "Market", "Mine"],
socket: webSocket
}
var handle_setup = function(data) {
setup_state.starting_deck = data.deck;
}
game_state = {
supply: [
{ name: "Copper", count: 46 },
{ name: "Silver", count: 38 },
{ name: "Gold", count: 30 },
{ name: "Estate", count: 8 },
{ name: "Duchery", count: 8 },
{ name: "Province", count: 8 },
{ name: "Curse", count: 10 },
{ name: "Cellar", count: 10},
{ name: "Moat", count: 10},
{ name: "Village", count: 10},
{ name: "Merchant", count: 10},
{ name: "Workshop", count: 10},
{ name: "Smithy", count: 10},
{ name: "Remodel", count: 10},
{ name: "Militia", count: 10},
{ name: "Market", count: 10},
{ name: "Mine", count: 10}
],
opponent_turn_state: {
actions: 1,
buys: 1,
coin: 0,
active: false
},
player_turn_state: {
actions: 1,
buys: 1,
coin: 0,
active: true
},
player: {
hand: []
},
players: [
{
name: "YOU",
draw_pile_count: 10,
hand_count: 0
},
{
name: "Alice",
draw_pile_count: 10,
hand_count: 5,
},
{
name: "Bob",
draw_pile_count: 8,
hand_count: 5
},
{
name: "Mallory",
draw_pile_count: 10,
hand_count: 3
}
],
active_player: 0
}
var chat_state = {
players: [],
socket: webSocket
}
var handle_game_state = function(state) {
game_state = {
...game_state,
...state,
};
game_state.opponent_turn_state.active = game_state.active_player != my_player_id;
game_state.player_turn_state.active = game_state.active_player == my_player_id;
chat_state.players = state.players;
if (state.state == "Setup") {
setup_state.active = true;
game_state.active = false;
} else {
setup_state.active = false;
game_state.active = true;
}
}
webSocket.onopen = function(event) {
console.log("ws open");
//webSocket.send("HALLO");
};
webSocket.onmessage = function(event) {
var msg = JSON.parse(event.data);
if (msg.type == "Chat") {
let chatDiv = document.getElementById("chat");
let newmsg = document.createElement("li");
newmsg.innerHTML = msg.sender + ": " + msg.message;
chatDiv.append(newmsg);
//newmsg.scrollIntoView();
} else if (msg.type == "PlayerJoined") {
let chatDiv = document.getElementById("chat");
let newmsg = document.createElement("li");
newmsg.innerHTML = msg.player + " joined the game.";
chatDiv.append(newmsg);
//newmsg.scrollIntoView();
} else if (msg.type == "GameState") {
handle_game_state(msg);
} else if (msg.type == "GameSetup") {
handle_setup(msg.setup);
} else if (msg.type == "PlayerHand") {
game_state.player.hand = msg.hand;
} else {
console.log("event?");
console.log(event.data);
}
m.redraw();
}
return {
view: function(vnode) {
return [
m(SetupScreen, setup_state),
m(GameScreen, game_state),
m(Chat, chat_state)
]
}
}
}
m.mount(document.getElementById("game"), App)
</script>
</body>
</html>