diff --git a/BondageClub/Backgrounds/LewdgamblerEntrance.jpg b/BondageClub/Backgrounds/LewdgamblerEntrance.jpg
new file mode 100644
index 0000000000..53df510e1a
Binary files /dev/null and b/BondageClub/Backgrounds/LewdgamblerEntrance.jpg differ
diff --git a/BondageClub/Backgrounds/LewdgamblerRoom.jpg b/BondageClub/Backgrounds/LewdgamblerRoom.jpg
new file mode 100644
index 0000000000..958af63056
Binary files /dev/null and b/BondageClub/Backgrounds/LewdgamblerRoom.jpg differ
diff --git a/BondageClub/Backgrounds/LewdgamblerRoomGameTable.jpg b/BondageClub/Backgrounds/LewdgamblerRoomGameTable.jpg
new file mode 100644
index 0000000000..9b53d9bca5
Binary files /dev/null and b/BondageClub/Backgrounds/LewdgamblerRoomGameTable.jpg differ
diff --git a/BondageClub/Icons/Lewdgambler.png b/BondageClub/Icons/Lewdgambler.png
new file mode 100644
index 0000000000..73ba282fba
Binary files /dev/null and b/BondageClub/Icons/Lewdgambler.png differ
diff --git a/BondageClub/Screens/Room/Gambling/Gambling.js b/BondageClub/Screens/Room/Gambling/Gambling.js
index 8eed31e85a..7f578a899c 100644
--- a/BondageClub/Screens/Room/Gambling/Gambling.js
+++ b/BondageClub/Screens/Room/Gambling/Gambling.js
@@ -272,25 +272,25 @@ function GamblingSimpleDiceController(SimpleDiceState) {
 		if (GamblingPlayerDice > GamblingNpcDice) {
 			GamblingFirstSub.AllowItem = true;
 			GamblingFirstSub.Stage = 81;
-			}
+		}
 		if (GamblingPlayerDice < GamblingNpcDice) {
 			GamblingFirstSub.AllowItem = false;
 			GamblingFirstSub.Stage = 82;
-			}
+		}
 		if (GamblingPlayerDice == GamblingNpcDice) {
 			GamblingFirstSub.AllowItem = false;
 			GamblingFirstSub.Stage = 83;
-			}
+		}
 	} else if (SimpleDiceState == "win") {
-			GamblingFirstSub.Stage = 0;
-			ReputationProgress("Gambling", 1);
+		GamblingFirstSub.Stage = 0;
+		ReputationProgress("Gambling", 1);
 	} else if (SimpleDiceState == "lost") {
-			InventoryWearRandom(Player, "ItemArms");
-			GamblingFirstSub.Stage = 0;
+		InventoryWearRandom(Player, "ItemArms");
+		GamblingFirstSub.Stage = 0;
 	} else if (SimpleDiceState == "equal") {
-			InventoryRemove(Player, "ItemArms");
-			InventoryRemove(GamblingFirstSub, "ItemArms");
-			GamblingFirstSub.Stage = 0;
+		InventoryRemove(Player, "ItemArms");
+		InventoryRemove(GamblingFirstSub, "ItemArms");
+		GamblingFirstSub.Stage = 0;
 	}
 }
 
@@ -437,7 +437,7 @@ function GamblingTwentyOneController(TwentyOneState) {
 			GamblingFirstSub.AllowItem = true;
 			GamblingFirstSub.Stage = 0;
 			ReputationProgress("Gambling", 3);
-			}
+		}
 
 		GamblingPlayerDiceStack = [];
 		GamblingNpcDiceStack = [];
@@ -455,7 +455,7 @@ function GamblingTwentyOneController(TwentyOneState) {
 			GamblingFirstSub.Appearance = GamblingAppearanceFirst.slice();
 			CharacterRefresh(GamblingFirstSub);
 			GamblingFirstSub.Stage = 0;
-			}
+		}
 
 		GamblingPlayerDiceStack = [];
 		GamblingNpcDiceStack = [];
@@ -472,75 +472,75 @@ function GamblingTwentyOneController(TwentyOneState) {
  * @param {"new" | "fox" | "hunter" | "NextDice" | "player_fox_win" | "player_fox_lost" | "player_hunter_lost" | "player_hunter_win"} FoxState - The current state of the game
  */
 function GamblingFoxController(FoxState) {
-		if (FoxState == "new") {
-			GamblingPlayerDiceStack = [];
-			GamblingNpcDiceStack = [];
-			GamblingShowMoney = true;
-		} else if (FoxState == "fox") {
-			GamblingPlayerIsFox = true;
-			GamblingPlayerDice = Math.floor(Math.random() * 6) + 1;
-			GamblingPlayerDiceStack[GamblingPlayerDiceStack.length] = GamblingPlayerDice;
-			GamblingMoneyBet = 5;
+	if (FoxState == "new") {
+		GamblingPlayerDiceStack = [];
+		GamblingNpcDiceStack = [];
+		GamblingShowMoney = true;
+	} else if (FoxState == "fox") {
+		GamblingPlayerIsFox = true;
+		GamblingPlayerDice = Math.floor(Math.random() * 6) + 1;
+		GamblingPlayerDiceStack[GamblingPlayerDiceStack.length] = GamblingPlayerDice;
+		GamblingMoneyBet = 5;
+		GamblingSecondSub.Stage = 101;
+	} else if (FoxState == "hunter") {
+		GamblingPlayerIsFox = false;
+		GamblingMoneyBet = 5;
+		CharacterChangeMoney(Player, GamblingMoneyBet * -1);
+		GamblingNpcDice = Math.floor(Math.random() * 6) + 1;
+		GamblingNpcDiceStack[GamblingNpcDiceStack.length] = GamblingNpcDice;
+		GamblingSecondSub.Stage = 101;
+	} else if (FoxState == "NextDice") {
+		GamblingPlayerDice = Math.floor(Math.random() * 6) + 1;
+		GamblingPlayerDiceStack[GamblingPlayerDiceStack.length] = GamblingPlayerDice;
+		GamblingNpcDice = Math.floor(Math.random() * 6) + 1;
+		GamblingNpcDiceStack[GamblingNpcDiceStack.length] = GamblingNpcDice;
+		if (GamblingPlayerIsFox && GamblingDiceStackSum(GamblingPlayerDiceStack) >= 30) {
+			//player has won
+			GamblingSecondSub.Stage = 102;
+		} else if (!GamblingPlayerIsFox && GamblingDiceStackSum(GamblingNpcDiceStack) >= 30) {
+			//npc has won
+			GamblingSecondSub.Stage = 103;
+		} else if (GamblingPlayerIsFox && (GamblingDiceStackSum(GamblingPlayerDiceStack) <= GamblingDiceStackSum(GamblingNpcDiceStack))) {
+			//npc has won
+			GamblingSecondSub.Stage = 104;
+		} else if (!GamblingPlayerIsFox && (GamblingDiceStackSum(GamblingNpcDiceStack) <= GamblingDiceStackSum(GamblingPlayerDiceStack))) {
+			//player has won
+			GamblingSecondSub.Stage = 105;
+		} else {
+			//next dice
 			GamblingSecondSub.Stage = 101;
-		} else if (FoxState == "hunter") {
-			GamblingPlayerIsFox = false;
-			GamblingMoneyBet = 5;
-			CharacterChangeMoney(Player, GamblingMoneyBet * -1);
-			GamblingNpcDice = Math.floor(Math.random() * 6) + 1;
-			GamblingNpcDiceStack[GamblingNpcDiceStack.length] = GamblingNpcDice;
-			GamblingSecondSub.Stage = 101;
-		} else if (FoxState == "NextDice") {
-			GamblingPlayerDice = Math.floor(Math.random() * 6) + 1;
-			GamblingPlayerDiceStack[GamblingPlayerDiceStack.length] = GamblingPlayerDice;
-			GamblingNpcDice = Math.floor(Math.random() * 6) + 1;
-			GamblingNpcDiceStack[GamblingNpcDiceStack.length] = GamblingNpcDice;
-			if (GamblingPlayerIsFox && GamblingDiceStackSum(GamblingPlayerDiceStack) >= 30) {
-				//player has won
-				GamblingSecondSub.Stage = 102;
-			} else if (!GamblingPlayerIsFox && GamblingDiceStackSum(GamblingNpcDiceStack) >= 30) {
-				//npc has won
-				GamblingSecondSub.Stage = 103;
-			} else if (GamblingPlayerIsFox && (GamblingDiceStackSum(GamblingPlayerDiceStack) <= GamblingDiceStackSum(GamblingNpcDiceStack))) {
-				//npc has won
-				GamblingSecondSub.Stage = 104;
-			} else if (!GamblingPlayerIsFox && (GamblingDiceStackSum(GamblingNpcDiceStack) <= GamblingDiceStackSum(GamblingPlayerDiceStack))) {
-				//player has won
-				GamblingSecondSub.Stage = 105;
-			} else {
-				//next dice
-				GamblingSecondSub.Stage = 101;
-			}
-		} else if (FoxState == "player_fox_win") {
-			GamblingSecondSub.AllowItem = false;
-			GamblingSecondSub.CurrentDialog = GamblingSecondSub.CurrentDialog.replace("REPLACEMONEY", GamblingMoneyBet.toString());
-			CharacterChangeMoney(Player, GamblingMoneyBet);
-			ReputationProgress("Gambling", 2);
-			GamblingPlayerDiceStack = [];
-			GamblingNpcDiceStack = [];
-			GamblingShowMoney = false;
-		} else if (FoxState == "player_fox_lost") {
-			GamblingSecondSub.AllowItem = false;
-			InventoryWearRandom(Player, "ItemLegs");
-			InventoryWearRandom(Player, "ItemFeet");
-			InventoryWearRandom(Player, "ItemArms");
-			GamblingPlayerDiceStack = [];
-			GamblingNpcDiceStack = [];
-			GamblingShowMoney = false;
-		} else if (FoxState == "player_hunter_win") {
-			InventoryWearRandom(GamblingSecondSub, "ItemArms");
-			GamblingSecondSub.AllowItem = true;
-			GamblingSecondSub.CurrentDialog = GamblingSecondSub.CurrentDialog.replace("REPLACEMONEY", GamblingMoneyBet.toString());
-			CharacterChangeMoney(Player, GamblingMoneyBet);
-			ReputationProgress("Gambling", 1);
-			GamblingPlayerDiceStack = [];
-			GamblingNpcDiceStack = [];
-			GamblingShowMoney = false;
-		} else if (FoxState == "player_hunter_lost") {
-			GamblingSecondSub.AllowItem = false;
-			GamblingPlayerDiceStack = [];
-			GamblingNpcDiceStack = [];
-			GamblingShowMoney = false;
 		}
+	} else if (FoxState == "player_fox_win") {
+		GamblingSecondSub.AllowItem = false;
+		GamblingSecondSub.CurrentDialog = GamblingSecondSub.CurrentDialog.replace("REPLACEMONEY", GamblingMoneyBet.toString());
+		CharacterChangeMoney(Player, GamblingMoneyBet);
+		ReputationProgress("Gambling", 2);
+		GamblingPlayerDiceStack = [];
+		GamblingNpcDiceStack = [];
+		GamblingShowMoney = false;
+	} else if (FoxState == "player_fox_lost") {
+		GamblingSecondSub.AllowItem = false;
+		InventoryWearRandom(Player, "ItemLegs");
+		InventoryWearRandom(Player, "ItemFeet");
+		InventoryWearRandom(Player, "ItemArms");
+		GamblingPlayerDiceStack = [];
+		GamblingNpcDiceStack = [];
+		GamblingShowMoney = false;
+	} else if (FoxState == "player_hunter_win") {
+		InventoryWearRandom(GamblingSecondSub, "ItemArms");
+		GamblingSecondSub.AllowItem = true;
+		GamblingSecondSub.CurrentDialog = GamblingSecondSub.CurrentDialog.replace("REPLACEMONEY", GamblingMoneyBet.toString());
+		CharacterChangeMoney(Player, GamblingMoneyBet);
+		ReputationProgress("Gambling", 1);
+		GamblingPlayerDiceStack = [];
+		GamblingNpcDiceStack = [];
+		GamblingShowMoney = false;
+	} else if (FoxState == "player_hunter_lost") {
+		GamblingSecondSub.AllowItem = false;
+		GamblingPlayerDiceStack = [];
+		GamblingNpcDiceStack = [];
+		GamblingShowMoney = false;
+	}
 }
 
 /**
@@ -672,9 +672,9 @@ function GamblingDaredSixController(DaredSixState) {
 		}
 	} else if (DaredSixState == "fin") {
 		do {
-		GamblingNpcDice = Math.floor(Math.random() * 6) + 1;
-		GamblingNpcDiceStack[GamblingNpcDiceStack.length] = GamblingNpcDice;
-		GamblingMoneyBet++;
+			GamblingNpcDice = Math.floor(Math.random() * 6) + 1;
+			GamblingNpcDiceStack[GamblingNpcDiceStack.length] = GamblingNpcDice;
+			GamblingMoneyBet++;
 		} while (GamblingDiceStackSum(GamblingNpcDiceStack) <= GamblingDiceStackSum(GamblingPlayerDiceStack) && GamblingNpcDice != 6 );
 		if (GamblingNpcDice == 6)
 		{
diff --git a/BondageClub/Screens/Room/LewdgamblerEntrance/Dialog_NPC_Lewdgambler_Security.csv b/BondageClub/Screens/Room/LewdgamblerEntrance/Dialog_NPC_Lewdgambler_Security.csv
new file mode 100644
index 0000000000..7942137f46
--- /dev/null
+++ b/BondageClub/Screens/Room/LewdgamblerEntrance/Dialog_NPC_Lewdgambler_Security.csv
@@ -0,0 +1,20 @@
+0,0,,"Greetings miss. Welcome to Mistress Lewdgambler Palace! Please, come closer. I have to check your member card before you can enter.",,
+0,20,What is this place?,"So you’re a newcomer? Okay then, I will give you a quick tour. This is a closed gambling facility for people who are involved in domination relationships. You can play with other visitors, making a bets for money or bondage treats.",,
+0,900,Here it is,--- > (checks the card and lets you in),GoToHall(),MembershipStatus(1)
+0,0,What is a member card?,"This is a pass that proofs your membership status. It is required to enter a Palace. We want to make sure that our guest won’t be disturbed by narrow persons or vanillas, if you know what I mean.",,MembershipStatus()
+0,10,I don’t have any cards,There is an option to buy one. But I need to do some background check first. What is your name again?,,MembershipStatus()
+10,11,I’m DialogPlayerName,"Good. Give me a moment. … I see, you’re from the Bondage Club right?",,
+11,12,I am.,Great then. 50$ is a price for a membership status. You will get an entry card signed by Mistress Lewdgambler personally.,,
+12,900,Deal (pay 50$),Congratulations! Here is your pass and welcome to Lewdgamer Palace. Enjoy.,BuyMembership(),HasMoneyForMembership()
+12,999,"That is too much, I don't have that money.","Sorry to say that, but I can’t let you in. This is some kind of closed facility and we do care of our guests comfort. Now I will ask you to leave. Good bye!",,
+12,999,That is too much for a door pay.,"Sorry to say that, but I can’t let you in. This is some kind of closed facility and we do care of our guests comfort. Now I will ask you to leave. Good bye!",,
+11,999,What is Bondage Club?,"Oh, my apologies. I believe some confusion have place in our data. Sorry to say that, but I can’t let you in. This is some kind of closed facility and we do care of our guests comfort. Now I will ask you to leave. Good bye!",,
+999,0,(Leave),,DialogLeave();,
+900,0,Thanks,,DialogLeave();,
+20,21,(Next),To get in you also need to have a membership card.,,
+21,0,Got it.,"Now please, present your member card. I have plenty of people waiting.",,
+0,30,Is there an option to get in without a card?,Owners can take their slaves inside for free with some conditions. So if you’re owned by some Palace member then lucky you.,,MembershipStatus()
+30,31,(Next),(giggles) Anyway slaves are not allowed to gamble at Palace. However any other way to have fun is allowed.,,
+31,31,What are conditions for a slave?,"Pacified, collared, leashed, with strict hand restrains.",,
+31,0,Got it.,"Now please, present your member card. I have plenty of people waiting.",,
+0,0,(Leave),,DialogLeave();,
diff --git a/BondageClub/Screens/Room/LewdgamblerEntrance/LewdgamblerEntrance.js b/BondageClub/Screens/Room/LewdgamblerEntrance/LewdgamblerEntrance.js
new file mode 100644
index 0000000000..30cfd470f1
--- /dev/null
+++ b/BondageClub/Screens/Room/LewdgamblerEntrance/LewdgamblerEntrance.js
@@ -0,0 +1,63 @@
+'use strict';
+
+(function _() {
+	const {
+		integrateRoomToWorld,
+		setPlayerGameData,
+		readPlayerGameData,
+	} = __vash_utils;
+	let securityGuy = null;
+
+	const MEMBERSHIP_PRICE = 50;
+	const MEMBERSHIP_PROPERTY_KEY = 'Lewdgambler.isMember';
+	const Background = 'LewdgamblerEntrance';
+
+	const Load = () => {
+		// Default load
+		if (securityGuy == null) {
+			securityGuy = CharacterLoadNPC('NPC_Lewdgambler_Security');
+			securityGuy.Name = 'Security';
+			CharacterNaked(securityGuy);
+			InventoryWear(securityGuy, 'CorsetShirt', 'Cloth', 'Default');
+			InventoryWear(securityGuy, 'ShortPencilSkirt', 'ClothLower', 'Default');
+			InventoryWear(securityGuy, 'Pantyhose1', 'Socks', 'Default');
+			InventoryWear(securityGuy, 'GarterBelt2', 'Garters', 'Default');
+			InventoryWear(securityGuy, 'Heels3', 'Shoes', '#202020');
+		}
+	};
+
+	const Run = () => {
+		DrawCharacter(securityGuy, 750, 0, 1);
+		DrawCharacter(Player, 250, 0, 1);
+		if (Player.CanWalk()) DrawButton(1885, 25, 90, 90, '', 'White', 'Icons/Exit.png', TextGet("Leave"));
+	};
+
+	const Click = () => {
+		if (MouseIn(250, 0, 500, 1000)) CharacterSetCurrent(Player);
+		if (MouseIn(750, 0, 500, 1000)) CharacterSetCurrent(securityGuy);
+		if (MouseIn(1885, 25, 90, 90) && Player.CanWalk()) CommonSetScreen('Room', 'MainHall');
+	};
+
+	const MembershipStatus = (requestedStatus) => {
+		const result = readPlayerGameData(MEMBERSHIP_PROPERTY_KEY) == requestedStatus;
+		return result;
+	};
+
+	const HasMoneyForMembership = () =>
+		Player.Money >= MEMBERSHIP_PRICE;
+
+	const BuyMembership = () => {
+		if (HasMoneyForMembership()) {
+			CharacterChangeMoney(Player, -MEMBERSHIP_PRICE);
+			setPlayerGameData('Lewdgambler.isMember', '1');
+		}
+	};
+
+	const GoToHall = () =>
+		CommonSetScreen('Room', 'LewdgamblerGameBiddingDuel');
+		// CommonSetScreen('Room', 'LewdgamblerHall');
+
+	integrateRoomToWorld('LewdgamblerEntrance', {
+		Background, Load, Run, Click, BuyMembership, MembershipStatus, HasMoneyForMembership, GoToHall,
+	});
+})();
diff --git a/BondageClub/Screens/Room/LewdgamblerEntrance/Text_LewdgamblerEntrance.csv b/BondageClub/Screens/Room/LewdgamblerEntrance/Text_LewdgamblerEntrance.csv
new file mode 100644
index 0000000000..fe8fba9ce3
--- /dev/null
+++ b/BondageClub/Screens/Room/LewdgamblerEntrance/Text_LewdgamblerEntrance.csv
@@ -0,0 +1 @@
+Leave,"Leave"
diff --git a/BondageClub/Screens/Room/LewdgamblerGameBiddingDuel/Bubble.png b/BondageClub/Screens/Room/LewdgamblerGameBiddingDuel/Bubble.png
new file mode 100644
index 0000000000..eb898a4217
Binary files /dev/null and b/BondageClub/Screens/Room/LewdgamblerGameBiddingDuel/Bubble.png differ
diff --git a/BondageClub/Screens/Room/LewdgamblerGameBiddingDuel/Dialog_NPC_LewdgamblerBiddingGame_Loose.csv b/BondageClub/Screens/Room/LewdgamblerGameBiddingDuel/Dialog_NPC_LewdgamblerBiddingGame_Loose.csv
new file mode 100644
index 0000000000..d4da58a1c5
--- /dev/null
+++ b/BondageClub/Screens/Room/LewdgamblerGameBiddingDuel/Dialog_NPC_LewdgamblerBiddingGame_Loose.csv
@@ -0,0 +1,4 @@
+0,0,,"Oh dear! Looks like you lost it all. Such a poor girl.",,
+0,1,"(Mumble in the gag.)","Oh stop it. Keep those noises for the visitors, I'll just take your money and will go rob someone else. Guards, take her away!"
+1,2,"(Struggle in your restraints.)","[You lost this game completely. Opponent takes all money and leave you restrained to the will of Palace security.]"
+2,0,"(Submit to guards.)",,"HandleLostGame()"
diff --git a/BondageClub/Screens/Room/LewdgamblerGameBiddingDuel/Dialog_NPC_LewdgamblerBiddingGame_Win.csv b/BondageClub/Screens/Room/LewdgamblerGameBiddingDuel/Dialog_NPC_LewdgamblerBiddingGame_Win.csv
new file mode 100644
index 0000000000..1bfaccaeac
--- /dev/null
+++ b/BondageClub/Screens/Room/LewdgamblerGameBiddingDuel/Dialog_NPC_LewdgamblerBiddingGame_Win.csv
@@ -0,0 +1,2 @@
+0,0,,"[You won! (+50$)]"
+0,0,"Yay!",,"HandleWonGame()"
diff --git a/BondageClub/Screens/Room/LewdgamblerGameBiddingDuel/LewdgamblerGameBiddingDuel.js b/BondageClub/Screens/Room/LewdgamblerGameBiddingDuel/LewdgamblerGameBiddingDuel.js
new file mode 100644
index 0000000000..9b3f3027e2
--- /dev/null
+++ b/BondageClub/Screens/Room/LewdgamblerGameBiddingDuel/LewdgamblerGameBiddingDuel.js
@@ -0,0 +1,345 @@
+'use strict';
+
+(function _() {
+	const {
+		integrateRoomToWorld,
+		pipe,
+	} = __vash_utils;
+
+	const {
+		INITIAL_STATE,
+		BID_ZONE,
+		STAGE,
+		addBid,
+		bidForOther,
+		canAddBid,
+		canRemoveBid,
+		removeBid,
+		setPileSize,
+		setStage,
+		switchStageAfterBids,
+		applyOtherDecision,
+		roundEnd,
+		restrain,
+		escape,
+		hasWinner,
+	} = __lewdgambler_biddingGame;
+
+	let opponentGuy = null;
+	const Background = 'LewdgamblerRoomGameTable';
+	const PILE_SIZE = 25;
+
+	const lockWithTimer = (character, groupName) => {
+		const currentItem = InventoryGet(character, groupName);
+		if (!currentItem.Property || !currentItem.Property.LockedBy) {
+			InventoryLock(character, currentItem, {
+				Asset: AssetGet("Female3DCG", "ItemMisc", "TimerPadlock"),
+			});
+		}
+	};
+
+	const releaseCharacter = (character) => {
+		InventoryRemove(character, 'ItemFeet');
+		InventoryRemove(character, 'ItemBoots');
+		InventoryRemove(character, 'ItemArms');
+		InventoryRemove(character, 'ItemHands');
+		InventoryRemove(character, 'ItemMouth');
+		InventoryRemove(character, 'ItemMouth2');
+		InventoryRemove(character, 'ItemHead');
+	};
+
+	const applyEndgameStateTransition = (state, updatedState) => {
+		const isWinningTransition = !hasWinner(state) && hasWinner(updatedState);
+		if (!isWinningTransition) return;
+
+		if (updatedState.stage === STAGE.PLAYER_LOST) {
+			CharacterSetCurrent(opponentGuy);
+			CharacterLoadCSVDialog(opponentGuy, 'Screens/Room/LewdgamblerGameBiddingDuel/Dialog_NPC_LewdgamblerBiddingGame_Loose');
+			releaseCharacter(opponentGuy);
+		}
+
+		if (updatedState.stage === STAGE.PLAYER_WON) {
+			CharacterSetCurrent(opponentGuy);
+			CharacterLoadCSVDialog(opponentGuy, 'Screens/Room/LewdgamblerGameBiddingDuel/Dialog_NPC_LewdgamblerBiddingGame_Win');
+			releaseCharacter(Player);
+		}
+	};
+
+	const applyRestrains = (character, restrains) => {
+		if (restrains[BID_ZONE.LEGS] > 0) {
+			InventoryWear(character, 'Irish8Cuffs', 'ItemFeet');
+		} else {
+			InventoryRemove(character, 'ItemFeet');
+		}
+
+		if (restrains[BID_ZONE.LEGS] > 1) {
+			InventoryWear(character, 'BalletWedges', 'ItemBoots');
+		} else {
+			InventoryRemove(character, 'ItemBoots');
+		}
+
+		if (restrains[BID_ZONE.LEGS] > 2) {
+			lockWithTimer(character, 'ItemFeet');
+			lockWithTimer(character, 'ItemBoots');
+		} else {
+			InventoryUnlock(character, 'ItemFeet');
+			InventoryUnlock(character, 'ItemBoots');
+		}
+
+		if (restrains[BID_ZONE.HANDS] > 0) {
+			InventoryWear(character, 'CollarCuffs', 'ItemArms');
+		} else {
+			InventoryRemove(character, 'ItemArms');
+		}
+
+		if (restrains[BID_ZONE.HANDS] > 1) {
+			lockWithTimer(character, 'ItemArms');
+		} else {
+			InventoryUnlock(character, 'ItemArms');
+		}
+
+		if (restrains[BID_ZONE.HANDS] > 2) {
+			InventoryWear(character, 'PolishedMittens', 'ItemHands');
+		} else {
+			InventoryRemove(character, 'ItemHands');
+		}
+
+		if (restrains[BID_ZONE.HEAD] > 0) {
+			InventoryWear(character, 'ClothGag', 'ItemMouth');
+		} else {
+			InventoryRemove(character, 'ItemMouth');
+		}
+
+		if (restrains[BID_ZONE.HEAD] > 1) {
+			InventoryWear(character, 'MuzzleGag', 'ItemMouth2');
+		} else {
+			InventoryRemove(character, 'ItemMouth2');
+		}
+
+		if (restrains[BID_ZONE.HEAD] > 2) {
+			InventoryWear(character, 'LeatherBlindfold', 'ItemHead');
+		} else {
+			InventoryRemove(character, 'ItemHead');
+		}
+	};
+
+	let gameState = INITIAL_STATE;
+	const check = selector => selector(gameState);
+	const setState = update => {
+		const updatedState = update(gameState);
+		applyRestrains(Player, updatedState.ownRestrains);
+		applyRestrains(opponentGuy, updatedState.otherRestrains);
+		applyEndgameStateTransition(gameState, updatedState);
+		gameState = updatedState;
+	};
+
+	const DrawCasinoButton = (top, left, w, h, text, hint) => {
+		const hintText = hint ? TextGet(hint) : null;
+		return DrawButton(top, left, w, h, text.toString(), '#61a66f', '', hintText, false, 'White', '#53885d');
+	};
+
+	const Load = () => {
+		if (opponentGuy == null) {
+			opponentGuy = CharacterLoadNPC('');
+		}
+		setState(() => INITIAL_STATE);
+		setState(setStage(STAGE.ROUND_START));
+		setState(setPileSize(PILE_SIZE));
+	};
+
+	const renderOwnBidValue = value => {
+		if (gameState.stage === STAGE.ROUND_START) return '-';
+		return value;
+	};
+
+	const renderOpponentBidValue = value => {
+		if (gameState.stage === STAGE.ROUND_START) return '-';
+		if (gameState.stage === STAGE.BIDDING) return '?';
+		return value;
+	};
+
+	const renderOpponentBubble = text => {
+		DrawImage("Screens/" + CurrentModule + "/" + CurrentScreen + "/Bubble.png", 1500, 16);
+		DrawText(text, 1725, 53, "Black", "Gray");
+	};
+
+	const Run = () => {
+		const {
+			otherBids,
+			otherPale,
+			ownBids,
+			ownPale,
+			stage,
+			winnerPale,
+		} = gameState;
+
+		// characters preview
+		DrawCharacter(opponentGuy, 1500, 75, 0.9);
+		DrawCharacter(Player, 50, 75, 0.9);
+
+		// Bidding labels
+		DrawTextFit(`${Player.Name} (${ownPale}$)`, 750, 295, 150, 'White', 'Black');
+		DrawTextFit(`${opponentGuy.Name} (${otherPale}$)`, 750, 600, 150, 'White', 'Black');
+		DrawTextFit('Head', 900, 80, 150, 'White', 'Black');
+		DrawTextFit('Hands', 1075, 80, 150, 'White', 'Black');
+		DrawTextFit('Feet', 1250, 80, 150, 'White', 'Black');
+
+		// Player bidding zone
+		DrawCasinoButton(855, 250, 90, 90, renderOwnBidValue(ownBids[BID_ZONE.HEAD]));
+		DrawCasinoButton(1030, 250, 90, 90, renderOwnBidValue(ownBids[BID_ZONE.HANDS]));
+		DrawCasinoButton(1205, 250, 90, 90, renderOwnBidValue(ownBids[BID_ZONE.LEGS]));
+
+		// Opponent bidding zone
+		DrawCasinoButton(855, 555, 90, 90, renderOpponentBidValue(otherBids[BID_ZONE.HEAD]));
+		DrawCasinoButton(1030, 555, 90, 90, renderOpponentBidValue(otherBids[BID_ZONE.HANDS]));
+		DrawCasinoButton(1205, 555, 90, 90, renderOpponentBidValue(otherBids[BID_ZONE.LEGS]));
+
+		// Player bid controls for bidding stage
+		if (stage === STAGE.BIDDING) {
+			if (check(canAddBid)) DrawCasinoButton(855, 140, 90, 90, '+', "AddHead");
+			if (check(canRemoveBid(BID_ZONE.HEAD))) DrawCasinoButton(855, 360, 90, 90, '-', "RemoveHead");
+			if (check(canAddBid)) DrawCasinoButton(1030, 140, 90, 90, '+', "AddHands");
+			if (check(canRemoveBid(BID_ZONE.HANDS))) DrawCasinoButton(1030, 360, 90, 90, '-', "RemoveHands");
+			if (check(canAddBid)) DrawCasinoButton(1205, 140, 90, 90, '+', "AddFeet");
+			if (check(canRemoveBid(BID_ZONE.LEGS))) DrawCasinoButton(1205, 360, 90, 90, '-', "RemoveFeet");
+		}
+
+		// Winner's pale nocice
+		DrawTextFit(`Winner gains additional (${winnerPale}$)`, 875, 750, 400, 'White', 'Black');
+
+		// Generic actions zone
+		if (stage === STAGE.BIDDING) {
+			renderOpponentBubble('I\'m done. Your move?');
+			DrawButton(1010, 850, 200, 90, 'Confirm', 'White', '', TextGet("ConfirmBidding"));
+		}
+
+		if (stage === STAGE.ROUND_START) {
+			renderOpponentBubble('Scared, you chicken?');
+			DrawButton(1010, 850, 200, 90, 'Go on', 'White', '', TextGet("StartRound"));
+			if (Player.CanWalk()) DrawButton(790, 850, 200, 90, 'Leave', 'White', '', TextGet("Leave"));
+		}
+
+		if (stage === STAGE.WIN_DECISION) {
+			renderOpponentBubble('The round is yours');
+
+			// Release actions zone
+			DrawButton(75, 140, 400, 90, 'Release head', 'White', '', '');
+			DrawButton(75, 250, 400, 90, 'Release hands', 'White', '', '');
+			DrawButton(75, 360, 400, 90, 'Release feet', 'White', '', '');
+
+			// Restrain actions zone
+			DrawButton(1525, 140, 400, 90, 'Restrain head', 'White', '', '');
+			DrawButton(1525, 250, 400, 90, 'Restrain hands', 'White', '', '');
+			DrawButton(1525, 360, 400, 90, 'Restrain feet', 'White', '', '');
+		}
+
+		if (stage === STAGE.LOOSE_ACCEPTANCE) {
+			renderOpponentBubble('Looser, huh.');
+			DrawButton(75, 690, 400, 90, 'Do your job...', 'White', '', '');
+		}
+	};
+
+	const Click = () => {
+		const {
+			stage,
+		} = gameState;
+
+		if (stage === STAGE.BIDDING) {
+			// bids controls
+			if (MouseIn(855, 140, 90, 90) && check(canAddBid)) setState(addBid(BID_ZONE.HEAD));
+			if (MouseIn(855, 360, 90, 90) && check(canRemoveBid(BID_ZONE.HEAD))) setState(removeBid(BID_ZONE.HEAD));
+			if (MouseIn(1030, 140, 90, 90) && check(canAddBid)) setState(addBid(BID_ZONE.HANDS));
+			if (MouseIn(1030, 360, 90, 90) && check(canRemoveBid(BID_ZONE.HANDS))) setState(removeBid(BID_ZONE.HANDS));
+			if (MouseIn(1205, 140, 90, 90) && check(canAddBid)) setState(addBid(BID_ZONE.LEGS));
+			if (MouseIn(1205, 360, 90, 90) && check(canRemoveBid(BID_ZONE.LEGS))) setState(removeBid(BID_ZONE.LEGS));
+
+			// proceed buttons
+			if (MouseIn(1010, 850, 200, 90)) {
+				// cheat should go here
+				// setState(setStage(STAGE.CHEAT_DECISION));
+				setState(switchStageAfterBids);
+			}
+		}
+
+		if (stage === STAGE.ROUND_START) {
+			// leave button
+			if (MouseIn(790, 850, 200, 90)) {
+				if (Player.CanWalk()) CommonSetScreen('Room', 'MainHall');
+			}
+			// proceed buttons
+			if (MouseIn(1010, 850, 200, 90)) {
+				setState(bidForOther);
+				setState(setStage(STAGE.BIDDING));
+			}
+		}
+
+		if (stage === STAGE.LOOSE_ACCEPTANCE) {
+			// loose acceptance buttons
+			if (MouseIn(75, 690, 400, 90)) {
+				setState(pipe(
+					applyOtherDecision,
+					roundEnd,
+				));
+			}
+		}
+
+		if (stage === STAGE.WIN_DECISION) {
+			// release buttons
+			if (MouseIn(75, 140, 400, 90)) {
+				setState(pipe(
+					escape(BID_ZONE.HEAD),
+					roundEnd,
+				));
+			}
+			if (MouseIn(75, 250, 400, 90)) {
+				setState(pipe(
+					escape(BID_ZONE.HANDS),
+					roundEnd,
+				));
+			}
+			if (MouseIn(75, 360, 400, 90)) {
+				setState(pipe(
+					escape(BID_ZONE.LEGS),
+					roundEnd,
+				));
+			}
+
+			// restrain buttons
+			if (MouseIn(1525, 140, 400, 90)) {
+				setState(pipe(
+					restrain(BID_ZONE.HEAD),
+					roundEnd,
+				));
+			}
+			if (MouseIn(1525, 250, 400, 90)) {
+				setState(pipe(
+					restrain(BID_ZONE.HANDS),
+					roundEnd,
+				));
+			}
+			if (MouseIn(1525, 360, 400, 90)) {
+				setState(pipe(
+					restrain(BID_ZONE.LEGS),
+					roundEnd,
+				));
+			}
+		}
+	};
+
+	const HandleLostGame = () => {
+		// change to moving to casino hall so maids can't reach you
+		CommonSetScreen('Room', 'MainHall');
+		CharacterChangeMoney(Player, -PILE_SIZE);
+		DialogLeave();
+	};
+
+	const HandleWonGame = () => {
+		CommonSetScreen('Room', 'MainHall');
+		CharacterChangeMoney(Player, PILE_SIZE * 2);
+		DialogLeave();
+	};
+
+	integrateRoomToWorld('LewdgamblerGameBiddingDuel', {
+		Background, Load, Run, Click, HandleLostGame, HandleWonGame,
+	});
+})();
diff --git a/BondageClub/Screens/Room/LewdgamblerGameBiddingDuel/core.js b/BondageClub/Screens/Room/LewdgamblerGameBiddingDuel/core.js
new file mode 100644
index 0000000000..1eda2e130a
--- /dev/null
+++ b/BondageClub/Screens/Room/LewdgamblerGameBiddingDuel/core.js
@@ -0,0 +1,520 @@
+"use strict";
+
+/* eslint-disable semi */
+window.__lewdgambler_biddingGame = (function _() {
+	const {
+		pipe,
+		complement,
+		sum,
+		anyPass,
+	} = __vash_utils;
+
+	const STAGE = {
+		INITIAL: 'INITIAL',
+		ROUND_START: 'ROUND_START',
+		BIDDING: 'BIDDING',
+		CHEAT_DECISION: 'CHEAT_DECISION',
+		WIN_DECISION: 'WIN_DECISION',
+		LOOSE_ACCEPTANCE: 'LOOSE_ACCEPTANCE',
+		PLAYER_WON: 'PLAYER_WON',
+		PLAYER_LOST: 'PLAYER_LOST',
+	}
+
+	const CHEAT_CHANCE = 0.15
+	const BLUFF_CHANCE = 0.15
+	const MAX_RESTRAIN_LEVEL = 3
+
+	const RESTRAIN = {
+		HEAD_STUFFING: 'stuffing',
+		HEAD_CLEAVE: 'cleave',
+		HEAD_BLINDFOLD: 'blindfold',
+		FEET_TIED: 'tied-feet',
+		FEET_BOOTS: 'boots',
+		FEET_LOCKED: 'locked-feet',
+		HANDS_HANDCUFFS: 'handcuffs',
+		HANDS_TIED_BEHIND: 'armbinder',
+		HANDS_MITTENS: 'mittens',
+	}
+
+	const BID_ZONE = {
+		HEAD: 'head',
+		HANDS: 'hands',
+		LEGS: 'legs',
+	}
+
+	const DECISION = {
+		CHEAT_SKIP: 'CHEAT_SKIP',
+		CHEAT_HANDS: 'CHEAT_HANDS',
+		CHEAT_HEAD: 'CHEAT_HEAD',
+		CHEAT_FEET: 'CHEAT_FEET',
+		ESCAPE_HANDS: 'ESCAPE_HANDS',
+		ESCAPE_HEAD: 'ESCAPE_HEAD',
+		ESCAPE_FEET: 'ESCAPE_FEET',
+		RESTRAIN_HANDS: 'RESTRAIN_HANDS',
+		RESTRAIN_HEAD: 'RESTRAIN_HEAD',
+		RESTRAIN_FEET: 'RESTRAIN_FEET',
+	}
+
+	const RESTRAIN_STATS = {
+		[RESTRAIN.HEAD_STUFFING]: {
+			zone: BID_ZONE.HEAD,
+			level: 1,
+		},
+		[RESTRAIN.HEAD_CLEAVE]: {
+			zone: BID_ZONE.HEAD,
+			level: 2,
+		},
+		[RESTRAIN.HEAD_BLINDFOLD]: {
+			zone: BID_ZONE.HEAD,
+			level: 3,
+		},
+		[RESTRAIN.FEET_TIED]: {
+			zone: BID_ZONE.LEGS,
+			level: 1,
+		},
+		[RESTRAIN.FEET_BOOTS]: {
+			zone: BID_ZONE.LEGS,
+			level: 2,
+		},
+		[RESTRAIN.FEET_LOCKED]: {
+			zone: BID_ZONE.LEGS,
+			level: 3,
+		},
+		[RESTRAIN.HANDS_HANDCUFFS]: {
+			zone: BID_ZONE.HANDS,
+			level: 1,
+		},
+		[RESTRAIN.HANDS_TIED_BEHIND]: {
+			zone: BID_ZONE.HANDS,
+			level: 2,
+		},
+		[RESTRAIN.HANDS_MITTENS]: {
+			zone: BID_ZONE.HANDS,
+			level: 3,
+		},
+	}
+
+	const BID_STATUS = {
+		WIN: 'win',
+		LOOSE: 'loose',
+		DRAW: 'draw',
+	}
+
+	const INITIAL_STATE = {
+		ownPale: 0,
+		winnerPale: 0,
+		otherPale: 0,
+		stage: STAGE.INITIAL,
+		ownRestrains: {
+			[BID_ZONE.HEAD]: 0,
+			[BID_ZONE.HANDS]: 0,
+			[BID_ZONE.LEGS]: 1,
+		},
+		otherRestrains: {
+			[BID_ZONE.HEAD]: 0,
+			[BID_ZONE.HANDS]: 0,
+			[BID_ZONE.LEGS]: 1,
+		},
+		ownBids: {
+			[BID_ZONE.HEAD]: 0,
+			[BID_ZONE.HANDS]: 0,
+			[BID_ZONE.LEGS]: 0,
+		},
+		otherBids: {
+			[BID_ZONE.HEAD]: 0,
+			[BID_ZONE.HANDS]: 0,
+			[BID_ZONE.LEGS]: 0,
+		},
+	}
+
+	const randomInt = (min, max) =>
+		Math.floor(Math.random() * (max  + 1 - min) + min)
+
+	const checkRestrain = restrainName => restrains => {
+		const { zone, level } = (RESTRAIN_STATS[restrainName] || {})
+		return (restrains[zone] || 0) >= level
+	}
+
+	const hasRestrain = restrainName => state =>
+		checkRestrain(restrainName)(state.ownRestrains);
+	const hasRestrains = restrains => state =>
+		restrains.some(restrainName => hasRestrain(restrainName)(state))
+
+	const hasOtherRestrain = restrainName => state =>
+		checkRestrain(restrainName)(state.otherRestrains);
+	const hasOtherRestrains = restrains => state =>
+		restrains.some(restrainName => hasOtherRestrain(restrainName)(state))
+
+	// @TODO: add some intelligence here
+	const rollBid = pale => {
+		const max = Math.floor(pale / 3)
+		return {
+			[BID_ZONE.HEAD]: randomInt(0, max),
+			[BID_ZONE.HANDS]: randomInt(0, max),
+			[BID_ZONE.LEGS]: randomInt(0, max),
+		}
+	}
+
+	const bidsTotal = bids => sum(Object.values(bids))
+
+	const canRestrain = zone => state => (state.otherRestrains[zone] || 0) < MAX_RESTRAIN_LEVEL
+	const canOtherRestrain = zone => state => (state.ownRestrains[zone] || 0) < MAX_RESTRAIN_LEVEL
+	const canEscape = zone => state => {
+		if (zone === BID_ZONE.LEGS && hasRestrains([RESTRAIN.FEET_LOCKED])(state)) {
+			return false
+		}
+
+		return state.ownRestrains[zone] > 0
+	}
+	const canOtherEscape = zone => state => {
+		if (zone === BID_ZONE.LEGS && hasOtherRestrains([RESTRAIN.FEET_LOCKED])(state)) {
+			return false
+		}
+
+		return state.otherRestrains[zone] > 0
+	}
+	const canLeave = complement(hasRestrains([RESTRAIN.FEET_TIED]))
+	const canCheat = complement(hasRestrains([RESTRAIN.HANDS_HANDCUFFS]))
+	const canBluff = complement(hasRestrains([RESTRAIN.HEAD_STUFFING]))
+	const canRemoveBid = zone => state => state.ownBids[zone] > 0
+	const canAddBid = state => state.ownPale - bidsTotal(state.ownBids) > 0
+	const isWinner = hasOtherRestrains([RESTRAIN.HEAD_BLINDFOLD, RESTRAIN.HANDS_MITTENS])
+	const isLooser = hasRestrains([RESTRAIN.HEAD_BLINDFOLD, RESTRAIN.HANDS_MITTENS])
+	const hasWinner = anyPass([isWinner, isLooser])
+	const dropBids = state => ({
+		...state,
+		ownBids: { [BID_ZONE.HEAD]: 0, [BID_ZONE.HANDS]: 0, [BID_ZONE.LEGS]: 0 },
+		otherBids: { [BID_ZONE.HEAD]: 0, [BID_ZONE.HANDS]: 0, [BID_ZONE.LEGS]: 0 },
+	})
+	const bidForOther = state => ({
+		...state,
+		otherBids: rollBid(state.otherPale),
+	})
+	const addBid = zone => state => ({
+		...state,
+		ownBids: {
+			...state.ownBids,
+			[zone]: state.ownBids[zone] + 1,
+		},
+	})
+	const removeBid = zone => state => ({
+		...state,
+		ownBids: {
+			...state.ownBids,
+			[zone]: state.ownBids[zone] - 1,
+		},
+	})
+
+	const cheat = zone => state => {
+		const cheatSucceed = Math.random() < CHEAT_CHANCE
+		if (!cheatSucceed) {
+			return state
+		}
+		const ownZoneBid = state.ownBids[zone]
+		const otherZoneBid = state.otherBids[zone]
+		const diff = otherZoneBid - ownZoneBid
+		const ownBidSum = bidsTotal(state.ownBids)
+		const addedBid = Math.min(state.ownPale - ownBidSum, diff + 1)
+
+		return {
+			...state,
+			ownBids: {
+				...state.ownBids,
+				[zone]: ownZoneBid + addedBid,
+			},
+		}
+	}
+
+	const bluff = zone => state => {
+		const bluffSucceed = Math.random() < BLUFF_CHANCE
+		if (!bluffSucceed) {
+			return state
+		}
+
+		const currentBid = state.otherBids
+		const paleLeft = state.otherPale - bidsTotal(state.otherBids)
+		const addedBid = randomInt(0, paleLeft)
+
+		return {
+			...state,
+			otherBids: {
+				...state.otherBids,
+				[zone]: currentBid + addedBid,
+			},
+		}
+	}
+
+	const compareBids = zone => state => {
+		const ownBid = state.ownBids[zone]
+		const otherBid = state.otherBids[zone]
+
+		if (ownBid === otherBid) return BID_STATUS.DRAW
+		if (ownBid > otherBid) return BID_STATUS.WIN
+		return BID_STATUS.LOOSE
+	}
+
+	const getBidsStatuses = state => ({
+		[BID_ZONE.HEAD]: compareBids(BID_ZONE.HEAD)(state),
+		[BID_ZONE.HANDS]: compareBids(BID_ZONE.HANDS)(state),
+		[BID_ZONE.LEGS]: compareBids(BID_ZONE.LEGS)(state),
+	})
+
+	const getBidRoundStatus = state => {
+		const statuses = getBidsStatuses(state)
+		const winsAmount = Object.values(statuses)
+			.filter(x => x === BID_STATUS.WIN).length
+		const lossesAmount = Object.values(statuses)
+			.filter(x => x === BID_STATUS.LOOSE).length
+
+		if (winsAmount > lossesAmount) {
+			return BID_STATUS.WIN
+		} else if (winsAmount < lossesAmount) {
+			return BID_STATUS.LOOSE
+		}
+
+		return BID_STATUS.DRAW
+	}
+
+	const applyZoneBid = zone => state => {
+		const ownBid = state.ownBids[zone]
+		const otherBid = state.otherBids[zone]
+		const zoneStatus = compareBids(zone)(state)
+
+		switch (zoneStatus) {
+			case BID_STATUS.DRAW: {
+				return {
+					...state,
+					ownPale: Math.max(0, state.ownPale - ownBid),
+					otherPale: Math.max(0, state.otherPale - otherBid),
+					winnerPale: state.winnerPale + otherBid + ownBid,
+				}
+			}
+			case BID_STATUS.WIN: {
+				return {
+					...state,
+					ownPale: Math.max(0, state.ownPale - ownBid),
+					otherPale: Math.max(0, state.otherPale - otherBid + ownBid),
+					winnerPale: state.winnerPale + otherBid,
+				}
+			}
+			case BID_STATUS.LOOSE: {
+				return {
+					...state,
+					ownPale: Math.max(0, state.ownPale - ownBid + otherBid),
+					otherPale: Math.max(0, state.otherPale - otherBid),
+					winnerPale: state.winnerPale + ownBid,
+				}
+			}
+			default: return state
+		}
+	}
+
+	const recalculatePales = state => Object.values(BID_ZONE)
+		.reduce((acc, zone) => applyZoneBid(zone)(acc), state)
+
+	const applyBids = pipe(
+		recalculatePales,
+		dropBids,
+	)
+
+	const restrain = zone => state => ({
+		...state,
+		otherRestrains: {
+			...state.otherRestrains,
+			[zone]: state.otherRestrains[zone] + 1,
+		},
+	})
+
+	const otherRestrain = zone => state => ({
+		...state,
+		ownRestrains: {
+			...state.ownRestrains,
+			[zone]: state.ownRestrains[zone] + 1,
+		},
+	})
+
+	const escape = zone => state => ({
+		...state,
+		ownRestrains: {
+			...state.ownRestrains,
+			[zone]: state.ownRestrains[zone] - 1,
+		},
+	})
+
+	const otherEscape = zone => state => ({
+		...state,
+		otherRestrains: {
+			...state.otherRestrains,
+			[zone]: state.otherRestrains[zone] - 1,
+		},
+	})
+
+	const rollLooseDecision = state => {
+		const bidsStatus = getBidsStatuses(state)
+
+		const wins = {
+			[BID_ZONE.HANDS]: bidsStatus[BID_ZONE.HANDS] === BID_STATUS.LOOSE,
+			[BID_ZONE.HEAD]: bidsStatus[BID_ZONE.HEAD] === BID_STATUS.LOOSE,
+			[BID_ZONE.LEGS]: bidsStatus[BID_ZONE.LEGS] === BID_STATUS.LOOSE,
+		}
+
+		const options = [
+			wins[BID_ZONE.HANDS]
+				&& canOtherEscape(BID_ZONE.HANDS)(state)
+				&& DECISION.ESCAPE_HANDS,
+			wins[BID_ZONE.HEAD]
+				&& canOtherEscape(BID_ZONE.HEAD)(state)
+				&& DECISION.ESCAPE_HEAD,
+			wins[BID_ZONE.LEGS]
+				&& canOtherEscape(BID_ZONE.LEGS)(state)
+				&& DECISION.ESCAPE_FEET,
+			wins[BID_ZONE.HANDS]
+				&& canOtherRestrain(BID_ZONE.HANDS)(state)
+				&& DECISION.RESTRAIN_HANDS,
+			wins[BID_ZONE.HEAD]
+				&& canOtherRestrain(BID_ZONE.HEAD)(state)
+				&& DECISION.RESTRAIN_HEAD,
+			wins[BID_ZONE.LEGS]
+				&& canOtherRestrain(BID_ZONE.LEGS)(state)
+				&& DECISION.RESTRAIN_FEET,
+		].filter(Boolean)
+
+		const decisionIndex = randomInt(0, options.length - 1)
+		return options[decisionIndex]
+	}
+
+	const getCheatDecisions = state => canCheat(state)
+		? [DECISION.CHEAT_HANDS, DECISION.CHEAT_HEAD, DECISION.CHEAT_FEET, DECISION.CHEAT_SKIP]
+		: [DECISION.CHEAT_SKIP]
+
+	const getWinDecisions = state => {
+		const bidsStatus = getBidsStatuses(state)
+
+		const wins = {
+			[BID_ZONE.HANDS]: bidsStatus[BID_ZONE.HANDS] === BID_STATUS.WIN,
+			[BID_ZONE.HEAD]: bidsStatus[BID_ZONE.HEAD] === BID_STATUS.WIN,
+			[BID_ZONE.LEGS]: bidsStatus[BID_ZONE.LEGS] === BID_STATUS.WIN,
+		}
+
+		return [
+			wins[BID_ZONE.HANDS]
+				&& canEscape(BID_ZONE.HANDS)(state)
+				&& DECISION.ESCAPE_HANDS,
+			wins[BID_ZONE.HEAD]
+				&& canEscape(BID_ZONE.HEAD)(state)
+				&& DECISION.ESCAPE_HEAD,
+			wins[BID_ZONE.LEGS]
+				&& canEscape(BID_ZONE.LEGS)(state)
+				&& DECISION.ESCAPE_FEET,
+			wins[BID_ZONE.HANDS]
+				&& canRestrain(BID_ZONE.HANDS)(state)
+				&& DECISION.RESTRAIN_HANDS,
+			wins[BID_ZONE.HEAD]
+				&& canRestrain(BID_ZONE.HEAD)(state)
+				&& DECISION.RESTRAIN_HEAD,
+			wins[BID_ZONE.LEGS]
+				&& canRestrain(BID_ZONE.LEGS)(state)
+				&& DECISION.RESTRAIN_FEET,
+		].filter(Boolean)
+	}
+
+	const setStage = stage => state => ({ ...state, stage })
+	const setPileSize = pileSize => state => ({
+		...state,
+		ownPale: pileSize,
+		otherPale: pileSize,
+	})
+
+	const switchStageAfterBids = state => {
+		const roundStatus = getBidRoundStatus(state)
+		const newStage = roundStatus === BID_STATUS.DRAW
+			? STAGE.ROUND_START
+			: roundStatus === BID_STATUS.WIN
+				? STAGE.WIN_DECISION
+				: STAGE.LOOSE_ACCEPTANCE
+
+		return setStage(newStage)(state)
+	}
+
+	const applyOwnDecision = decision => state => {
+		switch (decision) {
+			case DECISION.RESTRAIN_HEAD:
+				return restrain(BID_ZONE.HEAD)(state)
+			case DECISION.RESTRAIN_HANDS:
+				return restrain(BID_ZONE.HANDS)(state)
+			case DECISION.RESTRAIN_FEET:
+				return restrain(BID_ZONE.LEGS)(state)
+			case DECISION.ESCAPE_HEAD:
+				return escape(BID_ZONE.HEAD)(state)
+			case DECISION.ESCAPE_HANDS:
+				return escape(BID_ZONE.HANDS)(state)
+			case DECISION.ESCAPE_FEET:
+				return escape(BID_ZONE.LEGS)(state)
+			default: return state
+		}
+	}
+
+	const applyOtherDecision = state => {
+		const decision = rollLooseDecision(state)
+
+		switch (decision) {
+			case DECISION.RESTRAIN_HEAD:
+				return otherRestrain(BID_ZONE.HEAD)(state)
+			case DECISION.RESTRAIN_HANDS:
+				return otherRestrain(BID_ZONE.HANDS)(state)
+			case DECISION.RESTRAIN_FEET:
+				return otherRestrain(BID_ZONE.LEGS)(state)
+			case DECISION.ESCAPE_HEAD:
+				return otherEscape(BID_ZONE.HEAD)(state)
+			case DECISION.ESCAPE_HANDS:
+				return otherEscape(BID_ZONE.HANDS)(state)
+			case DECISION.ESCAPE_FEET:
+				return otherEscape(BID_ZONE.LEGS)(state)
+			default: return state
+		}
+	}
+
+	const roundEnd = state => {
+		const newStage = isWinner(state)
+			? STAGE.PLAYER_WON
+			: isLooser(state)
+				? STAGE.PLAYER_LOST
+				: STAGE.ROUND_START
+
+		return pipe(
+			setStage(newStage),
+			applyBids,
+		)(state)
+	}
+
+	return {
+		BID_ZONE,
+		DECISION,
+		INITIAL_STATE,
+		RESTRAIN,
+		STAGE,
+		addBid,
+		applyBids,
+		applyOtherDecision,
+		applyOwnDecision,
+		bidForOther,
+		bluff,
+		canAddBid,
+		canBluff,
+		canLeave,
+		canRemoveBid,
+		cheat,
+		escape,
+		getBidRoundStatus,
+		getCheatDecisions,
+		getWinDecisions,
+		removeBid,
+		restrain,
+		roundEnd,
+		setPileSize,
+		setStage,
+		switchStageAfterBids,
+		checkRestrain,
+		hasWinner,
+	}
+})();
diff --git a/BondageClub/Screens/Room/LewdgamblerHall/Dialog_NPC_Lewdgambler_Hostess.csv b/BondageClub/Screens/Room/LewdgamblerHall/Dialog_NPC_Lewdgambler_Hostess.csv
new file mode 100644
index 0000000000..e69de29bb2
diff --git a/BondageClub/Screens/Room/LewdgamblerHall/LewdgamblerHall.js b/BondageClub/Screens/Room/LewdgamblerHall/LewdgamblerHall.js
new file mode 100644
index 0000000000..a6c0999977
--- /dev/null
+++ b/BondageClub/Screens/Room/LewdgamblerHall/LewdgamblerHall.js
@@ -0,0 +1,62 @@
+'use strict';
+
+(function _() {
+	const {
+		integrateRoomToWorld,
+		setPlayerGameData,
+		readPlayerGameData,
+	} = __vash_utils;
+	let hostessGuy = null;
+
+	const MEMBERSHIP_PRICE = 50;
+	const MEMBERSHIP_PROPERTY_KEY = 'Lewdgambler.isMember';
+	const Background = 'LewdgamblerHall';
+
+	const Load = () => {
+		// Default load
+		if (hostessGuy == null) {
+			hostessGuy = CharacterLoadNPC('NPC_Lewdgambler_Hostess');
+			hostessGuy.Name = 'Hostess girl';
+			CharacterNaked(hostessGuy);
+			InventoryWear(hostessGuy, 'CorsetShirt', 'Cloth', 'Default');
+			InventoryWear(hostessGuy, 'ShortPencilSkirt', 'ClothLower', 'Default');
+			InventoryWear(hostessGuy, 'Pantyhose1', 'Socks', 'Default');
+			InventoryWear(hostessGuy, 'GarterBelt2', 'Garters', 'Default');
+			InventoryWear(hostessGuy, 'Heels3', 'Shoes', '#202020');
+		}
+	};
+
+	const Run = () => {
+		DrawCharacter(hostessGuy, 750, 0, 1);
+		DrawCharacter(Player, 250, 0, 1);
+		if (Player.CanWalk()) DrawButton(1885, 25, 90, 90, '', 'White', 'Icons/Exit.png', TextGet("Leave"));
+	};
+
+	const Click = () => {
+		if (MouseIn(250, 0, 500, 1000)) CharacterSetCurrent(Player);
+		if (MouseIn(750, 0, 500, 1000)) CharacterSetCurrent(hostessGuy);
+		if (MouseIn(1885, 25, 90, 90) && Player.CanWalk()) CommonSetScreen('Room', 'MainHall');
+	};
+
+	const MembershipStatus = (requestedStatus) => {
+		const result = readPlayerGameData(MEMBERSHIP_PROPERTY_KEY) == requestedStatus;
+		return result;
+	};
+
+	const HasMoneyForMembership = () =>
+		Player.Money >= MEMBERSHIP_PRICE;
+
+	const BuyMembership = () => {
+		if (HasMoneyForMembership()) {
+			CharacterChangeMoney(Player, -MEMBERSHIP_PRICE);
+			setPlayerGameData('Lewdgambler.isMember', '1');
+		}
+	};
+
+	const GoToHall = () =>
+		CommonSetScreen('Room', 'LewdgamblerHall');
+
+	integrateRoomToWorld('LewdgamblerHall', {
+		Background, Load, Run, Click, BuyMembership, MembershipStatus, HasMoneyForMembership, GoToHall,
+	});
+})();
diff --git a/BondageClub/Screens/Room/LewdgamblerHall/Text_LewdgamblerHall.csv b/BondageClub/Screens/Room/LewdgamblerHall/Text_LewdgamblerHall.csv
new file mode 100644
index 0000000000..fe8fba9ce3
--- /dev/null
+++ b/BondageClub/Screens/Room/LewdgamblerHall/Text_LewdgamblerHall.csv
@@ -0,0 +1 @@
+Leave,"Leave"
diff --git a/BondageClub/Screens/Room/MainHall/MainHall.js b/BondageClub/Screens/Room/MainHall/MainHall.js
index b9977d85d0..3793793a54 100644
--- a/BondageClub/Screens/Room/MainHall/MainHall.js
+++ b/BondageClub/Screens/Room/MainHall/MainHall.js
@@ -239,6 +239,7 @@ function MainHallRun() {
 		DrawButton(1885, 505, 90, 90, "", "White", "Icons/Cell.png", TextGet("Cell"));
 
 		// Asylum, College & LARP battles
+		DrawButton(1525, 625, 90, 90, "", "White", "Icons/Lewdgambler.png", TextGet("Lewdgambler"));
 		if (!ManagementIsClubSlave()) DrawButton(1645, 625, 90, 90, "", "White", "Icons/Battle.png", TextGet("LARPBattle"));
 		if (!ManagementIsClubSlave()) DrawButton(1765, 625, 90, 90, "", "White", "Icons/College.png", TextGet("College"));
 		DrawButton(1885, 625, 90, 90, "", "White", "Icons/Asylum.png", TextGet("Asylum"));
@@ -389,6 +390,8 @@ function MainHallClick() {
 		if ((MouseX >= 1885) && (MouseX < 1975) && (MouseY >= 505) && (MouseY < 595)) MainHallWalk("Cell");
 
 		// Asylum & College
+		// if ((MouseX >= 1525) && (MouseX < 1615) && (MouseY >= 625) && (MouseY < 715)) MainHallWalk("LewdgamblerEntrance");
+		if ((MouseX >= 1525) && (MouseX < 1615) && (MouseY >= 625) && (MouseY < 715)) MainHallWalk("LewdgamblerGameBiddingDuel");
 		if ((MouseX >= 1645) && (MouseX < 1735) && (MouseY >= 625) && (MouseY < 715) && !ManagementIsClubSlave()) MainHallWalk("LARP");
 		if ((MouseX >= 1765) && (MouseX < 1855) && (MouseY >= 625) && (MouseY < 715) && !ManagementIsClubSlave()) MainHallWalk("CollegeEntrance");
 		if ((MouseX >= 1885) && (MouseX < 1975) && (MouseY >= 625) && (MouseY < 715)) MainHallWalk("AsylumEntrance");
diff --git a/BondageClub/Screens/Room/MainHall/Text_MainHall.csv b/BondageClub/Screens/Room/MainHall/Text_MainHall.csv
index 212e3bdca7..a744eadba3 100644
--- a/BondageClub/Screens/Room/MainHall/Text_MainHall.csv
+++ b/BondageClub/Screens/Room/MainHall/Text_MainHall.csv
@@ -16,6 +16,7 @@ Appearance,Change Appearance,
 Cell,Timer Cell,
 SlaveMarket,Slave Market,
 LookForTrouble,Look for Trouble,
+Lewdgambler, Lewdgambler Palace,
 Asylum,Asylum,
 College,Visit the College,
 LARPBattle,LARP Battles,
diff --git a/BondageClub/Screens/Room/utils.js b/BondageClub/Screens/Room/utils.js
new file mode 100644
index 0000000000..a489d07266
--- /dev/null
+++ b/BondageClub/Screens/Room/utils.js
@@ -0,0 +1,61 @@
+"use strict";
+
+window.__vash_utils = (function _() {
+	const randomInt = (min, max) =>
+		Math.floor(Math.random() * (max  + 1 - min) + min);
+
+	const integrateRoomToWorld = (roomName, props) => {
+		Object.entries(props).forEach(([prop, value]) => {
+			const key = `${roomName}${prop}`;
+			if (window[key] !== undefined) {
+				console.warn(`Attempt to override window property "${key}" while integrating room "${roomName}"`);
+			} else {
+				window[key] = value;
+			}
+		});
+	};
+
+	const setPlayerGameData = (path, value) => {
+		const pathChains = path.split('.');
+		if (!Player.Game) {
+			Player.Game = {};
+		}
+		let pointer = Player.Game;
+		while (pathChains.length > 1) {
+			const key = pathChains.shift();
+			if (!pointer[key]) pointer[key] = {};
+			pointer = pointer[key];
+		}
+		pointer[pathChains[0]] = value;
+		ServerAccountUpdate.QueueData({ Game: Player.Game }, true);
+	};
+
+	const readPlayerGameData = (path) => {
+		const pathChains = path.split('.');
+		if (!Player.Game) {
+			Player.Game = {};
+		}
+		return pathChains.reduce((pointer, key) => pointer && pointer[key], Player.Game);
+	};
+
+	const getRandomItem = (array) => {
+		const index = randomInt(0, array.length - 1);
+		return array[index];
+	};
+
+	const sum = list => list.reduce((a, i) => a + i, 0);
+	const complement = predicate => (...props) => !predicate(...props);
+	const pipe = (...funcs) => argument => funcs.reduce((arg, func) => func(arg), argument);
+	const anyPass = (predicates) => (...props) => predicates.some(p => p(...props));
+
+	return {
+		integrateRoomToWorld,
+		setPlayerGameData,
+		readPlayerGameData,
+		getRandomItem,
+		sum,
+		complement,
+		pipe,
+		anyPass,
+	};
+})();
diff --git a/BondageClub/Scripts/Drawing.js b/BondageClub/Scripts/Drawing.js
index 6361d050fc..3022d00dd4 100644
--- a/BondageClub/Scripts/Drawing.js
+++ b/BondageClub/Scripts/Drawing.js
@@ -954,35 +954,41 @@ function DrawText(Text, X, Y, Color, BackColor) {
  * @param {number} Width - Width of the component
  * @param {number} Height - Height of the component
  * @param {string} Label - Text to display in the button
- * @param {string} Color - Color of the component
+ * @param {string} BackgroundColor - Color of the component
  * @param {string} [Image] - URL of the image to draw inside the button, if applicable
  * @param {string} [HoveringText] - Text of the tooltip, if applicable
  * @param {boolean} [Disabled] - Disables the hovering options if set to true
+ * @param {string} [TextColor] - Foreground Color of the component
+ * @param {string} [HoverColor] - Hovered state color of the component
  * @returns {void} - Nothing
  */
-function DrawButton(Left, Top, Width, Height, Label, Color, Image, HoveringText, Disabled) {
+function DrawButton(Left, Top, Width, Height, Label, BackgroundColor, Image, HoveringText, Disabled, TextColor, HoverColor) {
 
 	if (ControllerActive == true) {
 		setButton(Left, Top);
 	}
 
+	const foregroundColor = TextColor || "black";
+	const accentColor = HoverColor || "Cyan";
+	const isHovered = (MouseX >= Left) && (MouseX <= Left + Width) && (MouseY >= Top) && (MouseY <= Top + Height);
+	const hoverEnabled = HoveringText != null;
 	// Draw the button rectangle (makes the background color cyan if the mouse is over it)
 	MainCanvas.beginPath();
 	MainCanvas.rect(Left, Top, Width, Height);
-	MainCanvas.fillStyle = ((MouseX >= Left) && (MouseX <= Left + Width) && (MouseY >= Top) && (MouseY <= Top + Height) && !CommonIsMobile && !Disabled) ? "Cyan" : Color;
+	MainCanvas.fillStyle = (isHovered && hoverEnabled && !CommonIsMobile && !Disabled) ? accentColor : BackgroundColor;
 	MainCanvas.fillRect(Left, Top, Width, Height);
 	MainCanvas.fill();
 	MainCanvas.lineWidth = 2;
-	MainCanvas.strokeStyle = 'black';
+	MainCanvas.strokeStyle = foregroundColor;
 	MainCanvas.stroke();
 	MainCanvas.closePath();
 
 	// Draw the text or image
-	DrawTextFit(Label, Left + Width / 2, Top + (Height / 2) + 1, Width - 4, "black");
+	DrawTextFit(Label, Left + Width / 2, Top + (Height / 2) + 1, Width - 4, foregroundColor);
 	if ((Image != null) && (Image != "")) DrawImage(Image, Left + 2, Top + 2);
 
 	// Draw the hovering text
-	if ((HoveringText != null) && (MouseX >= Left) && (MouseX <= Left + Width) && (MouseY >= Top) && (MouseY <= Top + Height) && !CommonIsMobile) {
+	if (hoverEnabled && isHovered && !CommonIsMobile) {
 		DrawHoverElements.push(() => DrawButtonHover(Left, Top, Width, Height, HoveringText));
 	}
 }
diff --git a/BondageClub/index.html b/BondageClub/index.html
index 22b1ecf867..be0d27e187 100644
--- a/BondageClub/index.html
+++ b/BondageClub/index.html
@@ -78,6 +78,7 @@
 <script src="Screens/Character/Title/Title.js"></script>
 <script src="Screens/Character/BackgroundSelection/BackgroundSelection.js"></script>
 <script src="Screens/Character/Relog/Relog.js"></script>
+<script src="Screens/Room/utils.js"></script>
 <script src="Screens/Room/MainHall/MainHall.js"></script>
 <script src="Screens/Room/Shop/Shop.js"></script>
 <script src="Screens/Room/Introduction/Introduction.js"></script>
@@ -122,6 +123,9 @@
 <script src="Screens/Room/Nursery/Nursery.js"></script>
 <script src="Screens/Room/Cafe/Cafe.js"></script>
 <script src="Screens/Room/Arcade/Arcade.js"></script>
+<script src="Screens/Room/LewdgamblerEntrance/LewdgamblerEntrance.js"></script>
+<script src="Screens/Room/LewdgamblerGameBiddingDuel/core.js"></script>
+<script src="Screens/Room/LewdgamblerGameBiddingDuel/LewdgamblerGameBiddingDuel.js"></script>
 <script src="Screens/MiniGame/HorseWalk/HorseWalk.js"></script>
 <script src="Screens/MiniGame/GetUp/GetUp.js"></script>
 <script src="Screens/MiniGame/Kidnap/Kidnap.js"></script>