1166 lines
		
	
	
		
			34 KiB
		
	
	
	
		
			HTML
		
	
	
	
	
	
			
		
		
	
	
			1166 lines
		
	
	
		
			34 KiB
		
	
	
	
		
			HTML
		
	
	
	
	
	
<!doctype html>
 | 
						|
<html>
 | 
						|
 | 
						|
<head>
 | 
						|
    <meta charset="utf-8">
 | 
						|
    <title>DnD</title>
 | 
						|
    <link rel="stylesheet" href="/static/main.css">
 | 
						|
<style type="text/css">
 | 
						|
#game {
 | 
						|
    display: grid;
 | 
						|
    grid-template-columns: auto 300px;
 | 
						|
    user-select: none;
 | 
						|
    position: relative;
 | 
						|
}
 | 
						|
 | 
						|
.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;
 | 
						|
    border-radius: 6px;
 | 
						|
}
 | 
						|
 | 
						|
img.card:hover {
 | 
						|
    box-shadow: 0 0 5px white;
 | 
						|
}
 | 
						|
 | 
						|
.player-hand {
 | 
						|
    height: 145px;
 | 
						|
    margin: 0;
 | 
						|
    display: grid;
 | 
						|
    justify-content: center;
 | 
						|
    grid-template-columns: repeat(auto-fit, minmax(10px, max-content));
 | 
						|
    padding-right: 115px;
 | 
						|
}
 | 
						|
 | 
						|
.player-hand img.card {
 | 
						|
    transform: rotate(3deg);
 | 
						|
    transition: transform 0.1s ease-in;
 | 
						|
}
 | 
						|
 | 
						|
.player-hand img.card:hover {
 | 
						|
    box-shadow: 0 0 5px blue;
 | 
						|
    transform: scale(1.2);
 | 
						|
    transition: transform 0.1s ease-in;
 | 
						|
}
 | 
						|
 | 
						|
.player-hand img.card:hover ~ img.card {
 | 
						|
    transform: translate(60px, 0) rotate(3deg);
 | 
						|
    transition: transform 0.1s ease-in;
 | 
						|
}
 | 
						|
 | 
						|
.player-hand img.card.selected {
 | 
						|
    transform: translate(0px, -40px) scale(1.1) !important;
 | 
						|
    box-shadow: 0 0 10px lime;
 | 
						|
}
 | 
						|
 | 
						|
.supply-pile.selected {
 | 
						|
    box-shadow: 0 0 10px lime;
 | 
						|
}
 | 
						|
 | 
						|
.chat-window {
 | 
						|
    border: 1px solid black;
 | 
						|
    width: 300px;
 | 
						|
    grid-column-start: 2;
 | 
						|
    grid-row-start: 1;
 | 
						|
    box-sizing: border-box;
 | 
						|
}
 | 
						|
 | 
						|
.chat-window h4 {
 | 
						|
    text-align: center;
 | 
						|
    margin-top: 0;
 | 
						|
    margin-bottom: 5px;
 | 
						|
}
 | 
						|
 | 
						|
.chat-window .players {
 | 
						|
    list-style: none;
 | 
						|
    padding: 0;
 | 
						|
    margin: 0 5px 5px;
 | 
						|
}
 | 
						|
 | 
						|
.chat-window .players li {
 | 
						|
}
 | 
						|
 | 
						|
#chat li[data-repeat]::after {
 | 
						|
    content: "x" attr(data-repeat);
 | 
						|
    margin-left: 10px;
 | 
						|
    background-color: deepskyblue;
 | 
						|
    color: white;
 | 
						|
    border-radius: 4px;
 | 
						|
}
 | 
						|
 | 
						|
.supply-area {
 | 
						|
    position: relative;
 | 
						|
    padding-left: 30px;
 | 
						|
    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;
 | 
						|
}
 | 
						|
 | 
						|
.supply-pile {
 | 
						|
    width: 90px;
 | 
						|
    position: relative;
 | 
						|
    height: 145px;
 | 
						|
    border-radius: 6px;
 | 
						|
    margin: 1px;
 | 
						|
}
 | 
						|
 | 
						|
.supply-pile::after {
 | 
						|
    position: absolute;
 | 
						|
    bottom: 20px;
 | 
						|
    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;
 | 
						|
    content: attr(data-count);
 | 
						|
}
 | 
						|
 | 
						|
 | 
						|
.draw-pile {
 | 
						|
    width: 90px;
 | 
						|
    position: relative;
 | 
						|
    height: 145px;
 | 
						|
    background-image: url(/static/images/cards/Card_back.jpg);
 | 
						|
    background-size: 100% 100%;
 | 
						|
    border-radius: 6px;
 | 
						|
}
 | 
						|
 | 
						|
.draw-pile::after {
 | 
						|
    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;
 | 
						|
    content: attr(data-count);
 | 
						|
}
 | 
						|
 | 
						|
.opponent-hand {
 | 
						|
    width: 90px;
 | 
						|
    position: relative;
 | 
						|
    height: 145px;
 | 
						|
}
 | 
						|
 | 
						|
.opponent-hand img.card {
 | 
						|
    margin-top: 0;
 | 
						|
    margin-left: 0;
 | 
						|
}
 | 
						|
 | 
						|
.setup-screen {
 | 
						|
}
 | 
						|
 | 
						|
.hidden {
 | 
						|
    display: none;
 | 
						|
}
 | 
						|
 | 
						|
.discard-pile {
 | 
						|
    width: 90px;
 | 
						|
    border: 1px dotted green;
 | 
						|
    height: 144px;
 | 
						|
    box-sizing: border-box;
 | 
						|
    position: relative;
 | 
						|
    border-radius: 5px;
 | 
						|
} 
 | 
						|
 
 | 
						|
.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 h3 {
 | 
						|
    position: relative;
 | 
						|
    top: 50px;
 | 
						|
    left: 30px;
 | 
						|
    transform-origin: 0 0;
 | 
						|
    transform: rotate(90deg);
 | 
						|
    padding: 0;
 | 
						|
    margin: 0;
 | 
						|
    width: 100px;
 | 
						|
}
 | 
						|
 | 
						|
.inplay-area.inactive {
 | 
						|
    height: 0;
 | 
						|
    transition: height 0.4s ease;
 | 
						|
    overflow: hidden;
 | 
						|
    padding-top: 0;
 | 
						|
}
 | 
						|
 | 
						|
.inplay-area img.card {
 | 
						|
    margin-left: -55px;
 | 
						|
    transform: translate(85px, 10px);
 | 
						|
}
 | 
						|
 | 
						|
.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;
 | 
						|
    position: relative;
 | 
						|
    min-height: 145px;
 | 
						|
}
 | 
						|
 | 
						|
.opponent-status {
 | 
						|
    grid-template-rows: 30px auto;
 | 
						|
    grid-template-columns: auto auto auto;
 | 
						|
    box-sizing: border-box;
 | 
						|
    margin: 0 5px;
 | 
						|
    border: 1px solid dimgray;
 | 
						|
    display: grid;
 | 
						|
    grid-gap: 2px;
 | 
						|
    padding: 2px;
 | 
						|
}
 | 
						|
 | 
						|
.opponent-status.active {
 | 
						|
    box-shadow: 0 0 10px red;
 | 
						|
}
 | 
						|
 | 
						|
.opponent-status:first-child {
 | 
						|
    margin-left: 0;
 | 
						|
}
 | 
						|
 | 
						|
.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;
 | 
						|
}
 | 
						|
 | 
						|
.trash {
 | 
						|
    height: 145px;
 | 
						|
    border: 1px solid saddlebrown;
 | 
						|
    width: 90px;
 | 
						|
    position: absolute;
 | 
						|
    text-align: right;
 | 
						|
    right: 0;
 | 
						|
    border-radius: 6px;
 | 
						|
}
 | 
						|
 | 
						|
.trash::after {
 | 
						|
    content: "Trash";
 | 
						|
    color: saddlebrown;
 | 
						|
    font-size: 14px;
 | 
						|
    position: absolute;
 | 
						|
    top: 50%;
 | 
						|
    left: 50%;
 | 
						|
    transform: translate(-50%, -50%);
 | 
						|
    white-space: nowrap;
 | 
						|
    z-index: -1;
 | 
						|
}
 | 
						|
 | 
						|
 #preview {
 | 
						|
    grid-column-start: 2;
 | 
						|
    grid-row-start: 1;
 | 
						|
    height: 290px;
 | 
						|
    margin-top: 500px;
 | 
						|
 }
 | 
						|
 | 
						|
#preview img {
 | 
						|
    height: 290px;
 | 
						|
    width: 180px;
 | 
						|
}
 | 
						|
 | 
						|
#modal {
 | 
						|
    display: none;
 | 
						|
    grid-row-start: 1;
 | 
						|
    grid-column-end: 2;
 | 
						|
    position: absolute;
 | 
						|
    top: 0;
 | 
						|
    left: 0;
 | 
						|
    width: 100%;
 | 
						|
    height: 100%;
 | 
						|
    background-color: rgba(1.0, 1.0, 1.0, 0.6);
 | 
						|
}
 | 
						|
 | 
						|
.modal-content {
 | 
						|
    width: 600px;
 | 
						|
    height: 300px;
 | 
						|
    position: relative;
 | 
						|
    top: 50%;
 | 
						|
    left: 50%;
 | 
						|
    transform: translate(-50%, -50%);
 | 
						|
    background-color: lightgray;
 | 
						|
    box-shadow: 0 0 10px;
 | 
						|
    border: 1px solid black;
 | 
						|
}
 | 
						|
 | 
						|
.modal-content h1 {
 | 
						|
    text-align: center;
 | 
						|
    margin-top: 1px;
 | 
						|
}
 | 
						|
 | 
						|
.rank {
 | 
						|
    display: flex;
 | 
						|
    border-bottom: 1px solid dimgray;
 | 
						|
    padding: 2px;
 | 
						|
}
 | 
						|
 | 
						|
.rank span:first-child {
 | 
						|
    width: 120px;
 | 
						|
}
 | 
						|
 | 
						|
.dialog {
 | 
						|
    position: absolute;
 | 
						|
    width: 500px;
 | 
						|
    border: 1px solid dimgray;
 | 
						|
    top: 50%;
 | 
						|
    z-index: 200;
 | 
						|
    background-color: rgba(1.0, 1.0, 1.0, 0.9);
 | 
						|
    left: 50%;
 | 
						|
    transform: translate(-50%, -50%);
 | 
						|
    box-shadow: 0 0 20px black;
 | 
						|
    display: grid;
 | 
						|
    grid-template-columns: auto auto;
 | 
						|
    color: white;
 | 
						|
    visibility: hidden;
 | 
						|
    grid-template-rows: 5fr auto;
 | 
						|
    transition: top 0.1s ease-in;
 | 
						|
}
 | 
						|
 | 
						|
.dialog.supply {
 | 
						|
    top: 80%;
 | 
						|
}
 | 
						|
 | 
						|
.dialog.hand {
 | 
						|
    top: 50%;
 | 
						|
}
 | 
						|
 | 
						|
.dialog img {
 | 
						|
    grid-row-start: 1;
 | 
						|
    grid-row-end: 3;
 | 
						|
    margin: 5px;
 | 
						|
    width: 180px;
 | 
						|
    height: 288px;
 | 
						|
}
 | 
						|
 | 
						|
.dialog button {
 | 
						|
    grid-row-start: 2;
 | 
						|
    grid-column-start: 2;
 | 
						|
    margin: 10px auto;
 | 
						|
}
 | 
						|
</style>
 | 
						|
<script src="/static/mithril.js"></script>
 | 
						|
</head>
 | 
						|
 | 
						|
<body>
 | 
						|
    <div id="dialog" class="dialog">
 | 
						|
        <img class="card" style="margin: 5px; width:180px; height:288px;" src="/static/images/cards/cellar.jpg">
 | 
						|
        <p style="margin: 20px;">Choose any number of cards to discard.</p>
 | 
						|
        <button onclick="dialog_confirm()">Confirm</button>
 | 
						|
    </div>
 | 
						|
    <div id="game"></div>
 | 
						|
    <script>
 | 
						|
        var turnStartSound = new Audio("/static/audio/chipsStack1.ogg"); 
 | 
						|
 | 
						|
        var resolve_request;
 | 
						|
        var enable_hand_selection = false;
 | 
						|
        var enable_supply_selection = false;
 | 
						|
 | 
						|
        var toggle_hand_selection = function(index) {
 | 
						|
            document.querySelectorAll(".player-hand img.card")[index].classList.toggle("selected");
 | 
						|
        }
 | 
						|
 | 
						|
        var toggle_supply_selection = function(index) {
 | 
						|
            document.querySelectorAll(".supply-area .supply-pile")[index].classList.toggle("selected");
 | 
						|
        }
 | 
						|
 | 
						|
        function dialog_confirm(ev) {
 | 
						|
            if (resolve_request.request.type == "GainCard") {
 | 
						|
                var selected = document.querySelector(".supply-pile.selected");
 | 
						|
                if (selected) {
 | 
						|
                    selected = parseInt(selected.dataset.index);
 | 
						|
                }
 | 
						|
 | 
						|
                document.querySelectorAll(".supply-pile.selected").forEach((c) => {
 | 
						|
                    c.classList.remove("selected");
 | 
						|
                });
 | 
						|
 | 
						|
                var reply = { type: "SupplyCardChosen", choice: selected };
 | 
						|
                var msg = { type: "ResolveReply", reply: reply };
 | 
						|
                webSocket.send(JSON.stringify(msg));
 | 
						|
                enable_supply_selection = false;
 | 
						|
             } else {
 | 
						|
                var selected = [];
 | 
						|
                
 | 
						|
                document.querySelectorAll(".player-hand img.card.selected").forEach((c) => {
 | 
						|
                    selected.push(parseInt(c.dataset.index));
 | 
						|
                    c.classList.remove("selected");
 | 
						|
                });
 | 
						|
                
 | 
						|
                var reply = { type: "HandCardsChosen", choice: selected };
 | 
						|
                var msg = { type: "ResolveReply", reply: reply };
 | 
						|
                webSocket.send(JSON.stringify(msg));
 | 
						|
                enable_hand_selection = false;
 | 
						|
            }
 | 
						|
            document.querySelector("#dialog").style.visibility = "hidden";
 | 
						|
            dialog.classList.remove("hand");
 | 
						|
            dialog.classList.remove("supply");
 | 
						|
        }
 | 
						|
 | 
						|
         var mouseenter = function(ev) {
 | 
						|
             var img = ev.target.src;
 | 
						|
             document.querySelector("#preview img").src = img;
 | 
						|
             document.querySelector("#preview img").style.visibility = "visible";
 | 
						|
         }
 | 
						|
 | 
						|
         var mouseleave = function(ev) {
 | 
						|
             document.querySelector("#preview img").style.visibility = "hidden";
 | 
						|
         }
 | 
						|
 | 
						|
         function handle_dnd(data) {
 | 
						|
            if (data.source == "Hand" && data.dest == "InPlay") {
 | 
						|
                var msg = { type: "PlayCard", name: "", index: data.index};
 | 
						|
                webSocket.send(JSON.stringify(msg));
 | 
						|
            } else if (data.source == "Hand" && data.dest == "Discard") {
 | 
						|
                var msg = { type: "Discard", index: data.index};
 | 
						|
                webSocket.send(JSON.stringify(msg));
 | 
						|
            } else if (data.source == "Supply" && data.dest == "Discard") {
 | 
						|
                var msg = { type: "GainCard", name: data.name, index: parseInt(data.index) };
 | 
						|
                webSocket.send(JSON.stringify(msg));
 | 
						|
            } else if (data.source == "DrawPile" && data.dest == "Hand") {
 | 
						|
                var msg = { type: "DrawCard" };
 | 
						|
                webSocket.send(JSON.stringify(msg));
 | 
						|
            } else if (data.source == "Hand" && data.dest == "Trash") {
 | 
						|
                var msg = { type: "TrashHand", index: data.index };
 | 
						|
                webSocket.send(JSON.stringify(msg));
 | 
						|
            } else {
 | 
						|
                console.log("handle_dnd: unhandled data", data);
 | 
						|
            }
 | 
						|
        }
 | 
						|
 | 
						|
        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", {class: "players" }, 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
 | 
						|
                        }),
 | 
						|
                    ),
 | 
						|
                    m("#preview", m("img")),
 | 
						|
                    ]
 | 
						|
                }
 | 
						|
            }
 | 
						|
        }
 | 
						|
 | 
						|
        function SupplyPile(initialVnode) {
 | 
						|
            var dragStart = function(ev) {
 | 
						|
                let data = {
 | 
						|
                    source: "Supply",
 | 
						|
                    name: ev.target.dataset.name,
 | 
						|
                    index: ev.target.dataset.index,
 | 
						|
                };
 | 
						|
                ev.dataTransfer.setData("text", JSON.stringify(data));
 | 
						|
            }
 | 
						|
 | 
						|
            var click = function(e) {
 | 
						|
                e.preventDefault();
 | 
						|
                if (enable_supply_selection) {
 | 
						|
                    toggle_supply_selection(parseInt(e.srcElement.parentElement.dataset.index));
 | 
						|
                }
 | 
						|
            }
 | 
						|
 
 | 
						|
            var doubleclick = function(ev) {
 | 
						|
                let msg = {
 | 
						|
                    type: "BuyCard",
 | 
						|
                    index: parseInt(ev.srcElement.parentElement.dataset.index),
 | 
						|
                }
 | 
						|
 | 
						|
                webSocket.send(JSON.stringify(msg));
 | 
						|
            }
 | 
						|
 | 
						|
            return {
 | 
						|
                view: function(vnode) {
 | 
						|
                    return m(".supply-pile",
 | 
						|
                        {
 | 
						|
                            "data-count": vnode.attrs.count,
 | 
						|
                            "data-name": vnode.attrs.name,
 | 
						|
                            "data-index": vnode.attrs.index,
 | 
						|
                            ondragstart: dragStart,
 | 
						|
                            draggable: true,
 | 
						|
                            onclick: click,
 | 
						|
                            ondblclick: doubleclick,
 | 
						|
                        },
 | 
						|
                        m("img", {
 | 
						|
                            class: "card",
 | 
						|
                            src: "/static/images/cards/" + vnode.attrs.name.toLowerCase() + ".jpg",
 | 
						|
                            draggable: false,
 | 
						|
                            onmouseenter: mouseenter,
 | 
						|
                            onmouseleave: mouseleave,
 | 
						|
                        })
 | 
						|
                    )
 | 
						|
                }
 | 
						|
            }
 | 
						|
        }
 | 
						|
 | 
						|
        
 | 
						|
        function SupplyArea(initialVnode) {
 | 
						|
            return {
 | 
						|
                view: function(vnode) {
 | 
						|
                    return m(".supply-area", 
 | 
						|
                        m("h3", "Supply"),
 | 
						|
                        vnode.attrs.supply.map(function(pile, i) {
 | 
						|
                            return m(SupplyPile, {...pile, index: i})
 | 
						|
                        })
 | 
						|
                    )
 | 
						|
                }
 | 
						|
            }
 | 
						|
        }
 | 
						|
 | 
						|
        
 | 
						|
        function TrashPile(initialVnode) {
 | 
						|
            var dragover = function(e) {
 | 
						|
                e.preventDefault();
 | 
						|
            }
 | 
						|
 | 
						|
            var drop = function(e) {
 | 
						|
                e.preventDefault();
 | 
						|
                var data = {
 | 
						|
                    dest: "Trash",
 | 
						|
                    ...JSON.parse(e.dataTransfer.getData("text")),
 | 
						|
                };
 | 
						|
 | 
						|
                handle_dnd(data);
 | 
						|
            }
 | 
						|
 | 
						|
            return {
 | 
						|
                view: function(vnode) {
 | 
						|
                    return m(".trash",
 | 
						|
                        {
 | 
						|
                            ondragover: dragover,
 | 
						|
                            ondrop: drop,
 | 
						|
                        });
 | 
						|
                }
 | 
						|
            }
 | 
						|
        }
 | 
						|
 | 
						|
 | 
						|
        function DiscardPile(initialVnode) {
 | 
						|
            var dragover = function(e) {
 | 
						|
                e.preventDefault();
 | 
						|
            }
 | 
						|
 | 
						|
            var drop = function(e) {
 | 
						|
                e.preventDefault();
 | 
						|
                var data = {
 | 
						|
                    dest: "Discard",
 | 
						|
                    ...JSON.parse(e.dataTransfer.getData("text")),
 | 
						|
                };
 | 
						|
 | 
						|
                handle_dnd(data);
 | 
						|
            }
 | 
						|
 | 
						|
            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",
 | 
						|
                        {
 | 
						|
                            ondragover: dragover,
 | 
						|
                            ondrop: drop,
 | 
						|
                        },
 | 
						|
                        c)
 | 
						|
                }
 | 
						|
            }
 | 
						|
        }
 | 
						|
 | 
						|
        function DrawPile(initialVnode) {
 | 
						|
            var dragStart = function(ev) {
 | 
						|
                //console.log(ev);
 | 
						|
                let data = {
 | 
						|
                    source: "DrawPile",
 | 
						|
                };
 | 
						|
                ev.dataTransfer.setData("text", JSON.stringify(data));
 | 
						|
            }
 | 
						|
 | 
						|
            var dragover = function(e) {
 | 
						|
                e.preventDefault();
 | 
						|
            }
 | 
						|
 | 
						|
            var drop = function(e) {
 | 
						|
                e.preventDefault();
 | 
						|
                console.log("drop", e);
 | 
						|
            }
 | 
						|
 | 
						|
            return {
 | 
						|
                view: function(vnode) {
 | 
						|
                    return m(".draw-pile",
 | 
						|
                        {
 | 
						|
                            draggable: vnode.attrs.draggable ? true : false,
 | 
						|
                            ondragstart: dragStart,
 | 
						|
                            ondragover: dragover,
 | 
						|
                            ondrop: drop,
 | 
						|
                            "data-count": vnode.attrs.count,
 | 
						|
                        }
 | 
						|
                    )
 | 
						|
                }
 | 
						|
            }
 | 
						|
        }
 | 
						|
 | 
						|
        function PlayerHand(initialVnode) {
 | 
						|
            var dragStart = function(ev) {
 | 
						|
                //console.log(ev);
 | 
						|
                let data = {
 | 
						|
                    source: "Hand",
 | 
						|
                    index: parseInt(ev.target.dataset.index),
 | 
						|
                };
 | 
						|
                ev.dataTransfer.setData("text", JSON.stringify(data));
 | 
						|
            }
 | 
						|
 | 
						|
            var dragover = function(e) {
 | 
						|
                e.preventDefault();
 | 
						|
            }
 | 
						|
 | 
						|
            var drop = function(e) {
 | 
						|
                e.preventDefault();
 | 
						|
                var data = {
 | 
						|
                    dest: "Hand",
 | 
						|
                    ...JSON.parse(e.dataTransfer.getData("text")),
 | 
						|
                };
 | 
						|
 | 
						|
                handle_dnd(data);
 | 
						|
            }
 | 
						|
 | 
						|
            var click = function(e) {
 | 
						|
                e.preventDefault();
 | 
						|
                console.log("hand card click", e.target.dataset.index);
 | 
						|
                if (enable_hand_selection) {
 | 
						|
                    toggle_hand_selection(parseInt(e.target.dataset.index));
 | 
						|
                }
 | 
						|
            }
 | 
						|
 | 
						|
            return {
 | 
						|
                view: function(vnode) {
 | 
						|
                    return m(".player-hand",
 | 
						|
                        {
 | 
						|
                            ondragover: dragover,
 | 
						|
                            ondrop: drop,
 | 
						|
                        },
 | 
						|
                        vnode.attrs.hand.map(function(card, i) {
 | 
						|
                            return m("img", {
 | 
						|
                                class: "card",
 | 
						|
                                src: "/static/images/cards/" + card.toLowerCase() + ".jpg",
 | 
						|
                                draggable: true,
 | 
						|
                                ondragstart: dragStart,
 | 
						|
                                "data-index": i,
 | 
						|
                                onmouseenter: mouseenter,
 | 
						|
                                onmouseleave: mouseleave,
 | 
						|
                                onclick: click,
 | 
						|
                            })
 | 
						|
                        })
 | 
						|
                    )
 | 
						|
                } 
 | 
						|
            }
 | 
						|
        }
 | 
						|
 | 
						|
        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) {
 | 
						|
            var dragover = function(e) {
 | 
						|
                e.preventDefault();
 | 
						|
            }
 | 
						|
 | 
						|
            var drop = function(e) {
 | 
						|
                e.preventDefault();
 | 
						|
                var data = {
 | 
						|
                    dest: "InPlay",
 | 
						|
                    ...JSON.parse(e.dataTransfer.getData("text")),
 | 
						|
                };
 | 
						|
 | 
						|
                handle_dnd(data);
 | 
						|
            }
 | 
						|
 | 
						|
            return {
 | 
						|
                view: function(vnode) {
 | 
						|
                    var active = vnode.attrs.active ? "" : "inactive";
 | 
						|
                    var cards = game_state.players[game_state.active_player].played_cards || [];
 | 
						|
                    return m(".inplay-area",
 | 
						|
                        {
 | 
						|
                            class: active,
 | 
						|
                            ondragover: dragover,
 | 
						|
                            ondrop: drop,
 | 
						|
                        },
 | 
						|
                        m("h3", "In-Play"),
 | 
						|
                        m(".turn-status",
 | 
						|
                            "Actions: " + vnode.attrs.actions + " | Buys: " + vnode.attrs.buys + " | ",
 | 
						|
                            m("img", {
 | 
						|
                                src: "/static/images/64px-Coin.png",
 | 
						|
                                style: "height: 16px; position: relative; top: 2px;",
 | 
						|
                            }),
 | 
						|
                            ": " + vnode.attrs.coin
 | 
						|
                        ),
 | 
						|
                        cards.map(function(card) {
 | 
						|
                            return m("img", {
 | 
						|
                                class: "card",
 | 
						|
                                src: "/static/images/cards/" + card.toLowerCase() + ".jpg",
 | 
						|
                                draggable: true,
 | 
						|
                                //ondragstart: dragStart,
 | 
						|
                                onmouseenter: mouseenter,
 | 
						|
                                onmouseleave: mouseleave,
 | 
						|
                            })
 | 
						|
                        })
 | 
						|
                    )
 | 
						|
                }
 | 
						|
            }
 | 
						|
        }
 | 
						|
 
 | 
						|
        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, draggable: true}),
 | 
						|
                        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)
 | 
						|
                        }),
 | 
						|
                        m(TrashPile),
 | 
						|
                    )
 | 
						|
                } 
 | 
						|
            }
 | 
						|
        }
 | 
						|
 | 
						|
        function GameScreen(initialVnode) {
 | 
						|
            return {
 | 
						|
                view: function(vnode) {
 | 
						|
                    var player_state = {
 | 
						|
                        ...vnode.attrs.turn_state,
 | 
						|
                        active: vnode.attrs.active_player == my_player_id,
 | 
						|
                    }
 | 
						|
                    var opponent_state = {
 | 
						|
                        ...vnode.attrs.turn_state,
 | 
						|
                        active: vnode.attrs.active_player != my_player_id,
 | 
						|
                    }
 | 
						|
 | 
						|
                    var cls = vnode.attrs.active ? "" : "hidden";
 | 
						|
                    return m(".game-screen", {class: cls},
 | 
						|
                        m(OpponentArea, vnode.attrs),
 | 
						|
                        m(InPlayArea, opponent_state),
 | 
						|
                        m(SupplyArea, vnode.attrs),
 | 
						|
                        m(InPlayArea, player_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",
 | 
						|
                                    onmouseenter: mouseenter,
 | 
						|
                                    onmouseleave: mouseleave,
 | 
						|
                                })
 | 
						|
                            })
 | 
						|
                        ),
 | 
						|
                        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",
 | 
						|
                                    onmouseenter: mouseenter,
 | 
						|
                                    onmouseleave: mouseleave,
 | 
						|
                                })
 | 
						|
                            })
 | 
						|
                        ),
 | 
						|
                        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",
 | 
						|
                                    onmouseenter: mouseenter,
 | 
						|
                                    onmouseleave: mouseleave,
 | 
						|
                                })
 | 
						|
                            })
 | 
						|
                        ),
 | 
						|
                        m("button", {onclick: start_click}, "Start Game")
 | 
						|
                   )
 | 
						|
                }
 | 
						|
            }
 | 
						|
        }
 | 
						|
 | 
						|
        function EndScreen(initialVnode) {
 | 
						|
            return {
 | 
						|
                view: function(vnode) {
 | 
						|
                    return m(".modal-content",
 | 
						|
                        m("h1", "Score"),
 | 
						|
                        gameover_state.map(function(rank) {
 | 
						|
                            return m(".rank",
 | 
						|
                                m("span", game_state.players[rank[0]].name),
 | 
						|
                                m("span", rank[1])
 | 
						|
                            )
 | 
						|
                        })
 | 
						|
                    )
 | 
						|
                }
 | 
						|
            }
 | 
						|
        }
 | 
						|
 | 
						|
        var game_state;
 | 
						|
        var setup_state;
 | 
						|
        var my_player_id = 0;
 | 
						|
        var webSocket;
 | 
						|
        var gameover_state;
 | 
						|
 | 
						|
        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", "Duchy", "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: [],
 | 
						|
                turn_state: {
 | 
						|
                    actions: 1,
 | 
						|
                    buys: 1,
 | 
						|
                    coin: 0,
 | 
						|
                },
 | 
						|
                player: {
 | 
						|
                    hand: []
 | 
						|
                },
 | 
						|
                players: [
 | 
						|
                    {
 | 
						|
                        name: "You",
 | 
						|
                        draw_pile_count: 10,
 | 
						|
                        hand_count: 3
 | 
						|
                    }
 | 
						|
                ],
 | 
						|
                active_player: 0
 | 
						|
            }
 | 
						|
 | 
						|
            var chat_state = {
 | 
						|
                players: [],
 | 
						|
                socket: webSocket
 | 
						|
            }
 | 
						|
 | 
						|
            var handle_game_state = function(state) {
 | 
						|
                var last_player = game_state.active_player;
 | 
						|
                game_state = {
 | 
						|
                    ...game_state,
 | 
						|
                    ...state,
 | 
						|
                };
 | 
						|
                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;
 | 
						|
                }
 | 
						|
 | 
						|
                if (last_player != game_state.active_player) {
 | 
						|
                    if (game_state.active_player == my_player_id) {
 | 
						|
                        turnStartSound.play();
 | 
						|
                    }
 | 
						|
                }
 | 
						|
            }
 | 
						|
 | 
						|
            var handle_resolve_request = function(request) {
 | 
						|
                var dlg = document.querySelector("#dialog");
 | 
						|
                resolve_request = request;
 | 
						|
 | 
						|
                document.querySelector("#dialog img").src = "/static/images/cards/" + request.card.toLowerCase() + ".jpg";
 | 
						|
 | 
						|
                if (request.request.type == "GainCard") {
 | 
						|
                    let cost = request.request?.filter?.MaxCost;
 | 
						|
                    if (cost) {
 | 
						|
                        document.querySelector("#dialog p").innerHTML = "Choose a card to gain with max cost of " + cost + ".";
 | 
						|
                    } else {
 | 
						|
                        document.querySelector("#dialog p").innerHTML = "Choose a card to gain.";
 | 
						|
                    }
 | 
						|
                    enable_supply_selection = true;
 | 
						|
                    dialog.classList.add("supply");
 | 
						|
                } else if (request.request.type == "ChooseHandCardsToDiscard") {
 | 
						|
                    document.querySelector("#dialog p").innerHTML = "Choose any number of cards to discard.";
 | 
						|
                    enable_hand_selection = true;
 | 
						|
                    dialog.classList.add("hand");
 | 
						|
                }
 | 
						|
 | 
						|
                dlg.style.visibility = "visible";
 | 
						|
            }
 | 
						|
 | 
						|
            var handle_gameover = function(msg) {
 | 
						|
                gameover_state = msg.score;
 | 
						|
                var modal = document.querySelector("#modal");
 | 
						|
                modal.style.display = "block";
 | 
						|
                m.mount(modal, EndScreen);
 | 
						|
            }
 | 
						|
   
 | 
						|
            webSocket.onopen = function(event) {
 | 
						|
                console.log("ws open");
 | 
						|
            };
 | 
						|
 | 
						|
            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 == "Notification") {
 | 
						|
                    let chatDiv = document.getElementById("chat");
 | 
						|
                    let last_element = document.querySelector("#chat li:last-child");
 | 
						|
                    if (last_element.innerText == msg.text) {
 | 
						|
                        last_element.dataset.repeat = (parseInt(last_element.dataset.repeat || 1)) + 1;
 | 
						|
                    } else {
 | 
						|
                        let newmsg = document.createElement("li");
 | 
						|
                        newmsg.innerHTML = msg.text;
 | 
						|
                        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 == "GameOver") {
 | 
						|
                    handle_gameover(msg);
 | 
						|
                } else if (msg.type == "PlayerHand") {
 | 
						|
                    game_state.player.hand = msg.hand;
 | 
						|
                } else if (msg.type == "PlayerId") {
 | 
						|
                    my_player_id = msg.id;
 | 
						|
                } else if (msg.type == "ResolveRequest") {
 | 
						|
                    handle_resolve_request(msg);
 | 
						|
                } else {
 | 
						|
                    console.log("event?");
 | 
						|
                    console.log(event.data);
 | 
						|
                }
 | 
						|
 | 
						|
                m.redraw();
 | 
						|
            }
 | 
						|
 | 
						|
            return {
 | 
						|
                view: function(vnode) {
 | 
						|
                    return [
 | 
						|
                        m(SetupScreen, setup_state),
 | 
						|
                        m(GameScreen, game_state),
 | 
						|
                        m("#modal"),
 | 
						|
                        m(Chat, chat_state)
 | 
						|
                    ]
 | 
						|
                }
 | 
						|
            }
 | 
						|
        }
 | 
						|
 | 
						|
        m.mount(document.getElementById("game"), App)
 | 
						|
    </script>
 | 
						|
</body>
 | 
						|
</html>
 |