#StackBounty: #python #python-3.x #game #role-playing-game creating procedural generated RPG Dungeon maps

Bounty: 50

Intro

I am building a rogue like RPG, currently it is still a work in progress, but I would like to get an intermediate review of my Procedural map generation code.

I’ve chosen to use the Binary Space Partitioning algo, because it would make my life easier in the future when I will add corridors to the rooms.

There are some TODO’s left,

  • Creating random rooms within the leaves
  • Adding corridors to connect the (random-sized) rooms

but they are not up for review.

Code

from queue import Queue
from random import choice, randint
from collections import namedtuple
from enum import Enum

Position = namedtuple("Position", ["y", "x"])

class Tile(Enum):
    WALL = '#'
    EMPTY = ' '
    ROCK = '.'

class Split(Enum):
    HORIZONTAL = 0
    VERTICAL   = 1

class Room():
    def __init__(self, lu, rd):
        self.lu = lu
        self.rd = rd

    @property
    def position_map(self):
        return self._get_positions()

    @property
    def border_map(self):
        return self._get_border()

    @property
    def width(self):
        return self.rd.x - self.lu.x

    @property
    def height(self):
        return self.rd.y - self.lu.y

    def _get_positions(self):
        return [
            Position(row, col)
            for col in range(self.lu.x + 1, self.rd.x) 
            for row in range(self.lu.y + 1, self.rd.y)
        ]

    def _get_border(self):
        return [Position(y, self.lu.x) for y in range(self.lu.y, self.rd.y)] + 
               [Position(y, self.rd.x) for y in range(self.lu.y, self.rd.y)] + 
               [Position(self.lu.y, x) for x in range(self.lu.x, self.rd.x)] + 
               [Position(self.rd.y, x) for x in range(self.lu.x, self.rd.x + 1)]

class Leaf():
    def __init__(self, lu, rd, parent, min_room_space):
        self.lu = lu
        self.rd = rd
        self.parent = parent
        self.min_room_space = min_room_space
        self._children = None
        self._sibling = None
        self._room = None

    @property
    def children(self):
        return self._children

    @children.setter
    def children(self, value):
        self._children = value

    @property
    def sibling(self):
        return self._sibling

    @sibling.setter
    def sibling(self, value):
        self._sibling = value

    @property
    def room(self):
        return self._room or self._generate_room()

    @property
    def width(self):
        return self.rd.x - self.lu.x

    @property
    def height(self):
        return self.rd.y - self.lu.y

    def _generate_room(self):
        #TODO create random sized room in the leaf
        room = Room(self.lu, self.rd)
        self._room = room
        return room

class Map():
    def __init__(self, width, height, min_room_space=10, split_threshold=1.25):
        self.width = width
        self.height = height
        self.lu = Position(0, 0)
        self.rd = Position(height-1, width-1)
        self.min_room_space = min_room_space
        self.split_threshold = split_threshold
        self._leaves = None
        self.board = [[Tile.ROCK.value] * (self.width) for _ in range(self.height)]

    @property
    def leaves(self):
        return self._leaves

    def generate(self):
        # Reset the board
        self.board = [[Tile.ROCK.value] * (self.width) for _ in range(self.height)]
        self._generate_leaves()
        for leaf in self.leaves:
            for pos in leaf.room.position_map:
                self.board[pos.y][pos.x] = Tile.EMPTY.value
            for pos in leaf.room.border_map:
                self.board[pos.y][pos.x] = Tile.WALL.value

        #TODO Create corridors by adding corridors from the children up to the highest in the tree

    def _generate_leaves(self):
        splitable = Queue()
        splitable.put(Leaf(self.lu, self.rd, None, self.min_room_space))
        leaves = Queue()
        while splitable.qsize() > 0:
            leaf = splitable.get()
            leaf_a, leaf_b = self._split(leaf)

            if leaf_a is None and leaf_b is None:
                leaves.put(leaf)
            else:
                leaf.children = (leaf_a, leaf_b)
                leaf_a.sibling = leaf_b
                leaf_b.sibling = leaf_a
                splitable.put(leaf_a)
                splitable.put(leaf_b)

        self._leaves = list(leaves.queue)

    def _split(self, leaf):
        if leaf.width / leaf.height >= self.split_threshold:
            return self._split_room(leaf, Split.HORIZONTAL)
        elif leaf.height / leaf.width >= self.split_threshold:
            return self._split_room(leaf, Split.VERTICAL)
        else:
            return self._split_room(leaf, choice([Split.VERTICAL, Split.HORIZONTAL]))

    def _split_room(self, leaf, direction):
        leaf_a = leaf_b = None
        if direction == Split.VERTICAL:
            if not leaf.height < self.min_room_space * 2:
                random_split = randint(leaf.lu.y + self.min_room_space, leaf.rd.y - self.min_room_space)
                leaf_a = Leaf(leaf.lu,
                              Position(random_split, leaf.rd.x),
                              leaf,
                              self.min_room_space)
                leaf_b = Leaf(Position(random_split + 1, leaf.lu.x),
                              leaf.rd,
                              leaf,
                              self.min_room_space)
        elif direction == Split.HORIZONTAL:
            if not leaf.width < self.min_room_space * 2:
                random_split = randint(leaf.lu.x + self.min_room_space, leaf.rd.x - self.min_room_space)
                leaf_a = Leaf(leaf.lu,
                              Position(leaf.rd.y, random_split),
                              leaf,
                              self.min_room_space)
                leaf_b = Leaf(Position(leaf.lu.y, random_split + 1),
                              leaf.rd,
                              leaf,
                              self.min_room_space)
        return leaf_a, leaf_b

    def __str__(self):
        return "n".join("".join(b) for b in self.board)

if __name__ == "__main__":
    m = Map(50, 50, 10)
    m.generate()
    print(m)

Example Output

##################################################
#              ##        ##           ##         #
#              ##        ##           ##         #
#              ##        ##           ##         #
#              ##        ##           ##         #
#              ##        ##           ##         #
#              ##        ##           ##         #
#              ##        ##           ##         #
#              ##        ##           ##         #
#              ##        ##           ##         #
#              ##        ##           ##         #
#              ##        ##           ##         #
#              ##        ##           ############
#              ##        ##           ############
#              ##        ##           ##         #
#              ##        ##           ##         #
########################################         #
########################################         #
#          ##         ##              ##         #
#          ##         ##              ##         #
#          ##         ##              ##         #
#          ##         ##              ##         #
#          ##         ##              ##         #
#          ##         ##              ##         #
#          ##         ##              ##         #
#          ##         ##              ##         #
#          ##         ##              ##         #
#          ##         ##              ##         #
#          ##         ##              ##         #
#          ##         ##              ##         #
#          ##         ##              ##         #
#          ##         ##              ############
#          ##         ##              ############
#          ##         ##              ##         #
#          ##         ##              ##         #
########################################         #
########################################         #
#           ##              ##        ##         #
#           ##              ##        ##         #
#           ##              ##        ##         #
#           ##              ##        ##         #
#           ##              ##        ##         #
#           ##              ##        ##         #
#           ##              ##        ##         #
#           ##              ##        ##         #
#           ##              ##        ##         #
#           ##              ##        ##         #
#           ##              ##        ##         #
#           ##              ##        ##         #
##################################################

Please critique any and all


Get this bounty!!!

#StackBounty: #python #game #animation #raspberry-pi Runner 1-bit game in Python like the offline chrome dino

Bounty: 50

I made this little game to test if my Nokia 5110 screen can handle games. As in handle many frames per second/game loops.

I am using a library made for this screen called Adafruit_Nokia_LCD

These are the imports for some context, if needed:

import Adafruit_Nokia_LCD as LCD
import Adafruit_GPIO.SPI as SPI
import threading
from PIL import Image
from PIL import ImageDraw
from PIL import ImageFont

This is how I load images. I have some black and white images, use a binary converter to turn them into a multiline string. These are some tamagotchi sprites for example:

tam1 = """00000000000000111100000
00001111000000111110000
00001111100001111110000
00011111100001111110000
00011111111111111111000
00011111111111111111000
00011111111111111111000
00111111111111111111000
00111111111111111111100
01001111000000011111110
01100000011110000000010
10110000111101000000001
10110000111110100000001
10110000111110100000001
10110000111110100000001
01110000011100100000001
01100000001111000000010
00100101000000000000010
00010011000000000000100
00001100000000000011110
00000011111111111100001
00000100000000000000001
00001000000000000011110
00001001000000000010000
00000111000000000010000
00000001000000000011100
00000010000000000100010
00000100000111111000010
00000100011000000111100
00000011100000000000000"""

There are two more sprites, for a walking animation called tam2 and tam3

I split them into lines:

tam1a = tam1.split('n')
tam2a = tam2.split('n')
tam3a = tam3.split('n')

I have a different thread running to capture key input, in which case is spacebar to jump:

key = "lol" #uhh this is the keyword for no input pretty much idk i just left it in
getch = _Getch()
def thread1():
    global key
    lock = threading.Lock()
    while True:
        with lock:
            key = getch() #this is some code that works just like the msvcrt version
threading.Thread(target = thread1).start()

This is the game loop, after setting some variables:

#jump variables 
dist = 0 #distance off the ground
gup = False #isjumping
#obstacle variables
xx = 0 #x position of the obstacle that loops through the screen

#other variables
score = 0;
ind = 0; #current frame of player, there is 3
extraspeed = 0; #makes the pillar go faster as your score rises
while True: #gameloop start
    draw.rectangle((0,0,LCD.LCDWIDTH,LCD.LCDHEIGHT), outline=255, fill=255)
    #clears the screen --^
    draw.text((0,0),str(score),font=font) #draw score
    extraspeed = floor(score / 100) #set extraspeed based on score
    if extraspeed > 10:
        extraspeed = 10
    score += 3
    draw.rectangle((xx,32,xx+3,42),outline=0,fill=0)
    xx = xx + 4 +extraspeed #move the pillar
    if xx >= 84:
        xx = -4;
    if key == ' ':
        if dist == 0:
            gup = True
            key = "lol" #if dist = 0 means its on the ground
            #the "lol" thing is something I left over for some reason, it means no key pressed
        else:
            key = "lol"
    if gup == True:
        if dist != 12: #jumps up to 12 pixels off the ground
            dist += 3
        else:
            gup = False #start falling if reached top
    else:
        if dist > 0:
            dist -= 3
        else:
            dist = 0
    #ind is the current animation sprite to draw
    if ind == 1:
        i = 12-dist #top left corner of drawn sprite y
        j = 60 #top left corner of drawn sprite x
        for line in tam1a:
            for c in line:
                if c == '1':
                    draw.point((j,i),fill=0)
                j+=1
            i+=1
            j=60 #make same as j at start
    if ind == 2:
        i = 12-dist #top left corner of drawn sprite y
        j = 60 #top left corner of drawn sprite x
        for line in tam2a:
            for c in line:
                if c == '1':
                    draw.point((j,i),fill=0)
                j+=1
            i+=1
            j=60 #make same as j at start
    if ind == 3:
        i = 12-dist #top left corner of drawn sprite y
        j = 60 #top left corner of drawn sprite x
        for line in tam3a:
            for c in line:
                if c == '1':
                    draw.point((j,i),fill=0)
                j+=1
            i+=1
            j=60 #make same as j at start

    ind += 1
    if ind == 4:
        ind = 1 #restart the animation
    draw.line((0,43,83,43),fill=0)
    draw.line((0,44,83,44),fill=0) #draw some ground
    if xx >= float(67) and xx<= float(80) and dist <= 7:
        break #Some simple collision detection

# Display image.
    disp.image(image) #draw everything
    disp.display() #display everything

    time.sleep(0.2) #5 frames per second
draw.text((40,10),'Hit',font=font) #got here if 'break' occurs
disp.image(image) #displays hit, which means game over and loop is broken
disp.display()

My problem:


First of all: This screen is connected through GPIO pins to my Raspberry-Pi.

What do you think of this method to load sprites?

How about drawing the sprites? I iterate through the string, line by line then character by character and draw the pixels respectively (0 means dont draw, 1 means draw black)

Now while this code works, I experience ghosting and image burn in my lcd screen. While I know the nokia 5110 screen wasn’t designed to be for a gaming system, can my code be optimized to a point of reducing this ghosting ?

What do you think of my key input getting method, is a seperate thread fine for this job?

Lastly, would a different drawing method allow me to reach 60 frames per second or is it just impossible due to the way this screen is manufactured?


Here’s a pic. Notice how there is image burn from the last frame? There is only one pillar, and only one frame of the player, but two appear. Is this fixable, or is it because of the screen only?

Image burn and ghosting


Get this bounty!!!

#StackBounty: #javascript #html #game #ecmascript-6 #dom Arkanoid/Breakout clone with ThreeJS

Bounty: 100

Here’s my attempt at an Arkanoid/Breakout clone using JavaScript with ThreeJS. The feedback I’m looking for is more on the code side; the game itself is a work in progress. You can take it for a spin by running the code snippet below.

(function() {
    "use strict";

    const paddleStates = {
        MOVING_LEFT : 0,
        MOVING_RIGHT : 1,
        STATIONARY : 2
    };

    function createMeshAtPosition(meshProperties, position) {
        let mesh = new THREE.Mesh(meshProperties.geometry, meshProperties.material);
        mesh.position.copy(position);
        return mesh;
    }

    function createFullScreenRenderer(elementId, settings) {
        let renderer = new THREE.WebGLRenderer({
            canvas: document.getElementById(elementId)
        });
        renderer.setPixelRatio(window.devicePixelRatio);
        renderer.setSize(window.innerWidth, window.innerHeight);
        renderer.setClearColor(settings.backgroundColor);
        return renderer;
    }

    function createCamera() {
        let camera = new THREE.PerspectiveCamera(
            90,
            window.innerWidth / window.innerHeight,
            0.1,
            3000);
        camera.position.set(0.0, 10.0, 0.0);
        camera.lookAt(0.0, 0.0, -10.0);
        return camera;
    }

    function makeResizeCallback(camera, renderer) {
        return function() {
            camera.aspect = window.innerWidth / window.innerHeight;
            camera.updateProjectionMatrix();
            renderer.setSize(window.innerWidth, window.innerHeight);
        };
    }

    function makeKeyDownCallback(paddle, speed) {
        return function(event) {
            if (paddle.state === paddleStates.STATIONARY) {
                if (event.key === "ArrowLeft") {
                    paddle.velocity.x = -speed;
                    paddle.state = paddleStates.MOVING_LEFT;
                } else if (event.key === "ArrowRight") {
                    paddle.velocity.x = speed;
                    paddle.state = paddleStates.MOVING_RIGHT;
                }
            }
        };
    }

    function makeKeyUpCallback(paddle) {
        return function(event) {
            if (paddle.state === paddleStates.MOVING_LEFT && event.key === "ArrowLeft") {
                paddle.velocity.x = 0.0;
                paddle.state = paddleStates.STATIONARY;
            } else if (paddle.state === paddleStates.MOVING_RIGHT && event.key === "ArrowRight") {
                paddle.velocity.x = 0.0;
                paddle.state = paddleStates.STATIONARY;
            }
        };
    }

    function updatePosition(gameObject) {
        gameObject.mesh.position.add(gameObject.velocity);
    }

    function resolveBallBlockCollision(ball, blockMesh, blockProperties, callback) {
        if ((ball.mesh.position.z + ball.radius > blockMesh.position.z - blockProperties.height / 2 &&
            (ball.mesh.position.z < blockMesh.position.z)) &&
            (ball.mesh.position.x > blockMesh.position.x - blockProperties.width / 2) &&
            (ball.mesh.position.x < blockMesh.position.x + blockProperties.width / 2) &&
            (ball.velocity.z > 0.0)) 
        {
            ball.velocity.z *= -1.0;
            callback();
            return true;
        }

        if ((ball.mesh.position.z - ball.radius < blockMesh.position.z + blockProperties.height / 2 &&
            (ball.mesh.position.z > blockMesh.position.z)) &&
            (ball.mesh.position.x > blockMesh.position.x - blockProperties.width / 2) &&
            (ball.mesh.position.x < blockMesh.position.x + blockProperties.width / 2) &&
            (ball.velocity.z < 0.0)) 
        {
            ball.velocity.z *= -1.0;
            callback();
            return true;
        }

        if ((ball.mesh.position.x + ball.radius > blockMesh.position.x - blockProperties.width / 2 &&
            (ball.mesh.position.x < blockMesh.position.x)) &&
            (ball.mesh.position.z > blockMesh.position.z - blockProperties.height / 2) &&
            (ball.mesh.position.z < blockMesh.position.z + blockProperties.height / 2) &&
            (ball.velocity.x > 0.0)) 
        {
            ball.velocity.x *= -1.0;
            callback();
            return true;
        }

        if ((ball.mesh.position.x - ball.radius < blockMesh.position.x + blockProperties.width / 2 &&
            (ball.mesh.position.x > blockMesh.position.x)) &&
            (ball.mesh.position.z > blockMesh.position.z - blockProperties.height / 2) &&
            (ball.mesh.position.z < blockMesh.position.z + blockProperties.height / 2) &&
            (ball.velocity.x < 0.0)) 
        {
            ball.velocity.x *= -1.0;
            callback();
            return true;
        }

        return false;
    }

    function main() {
        // Hard-coded "settings"
        let settings = {
            backgroundColor : 0x008888,
            paddleSpeed : 0.3,
            ballSpeed: 0.2
        };

        let paddle = {
            width : 4,
            height : 1,
            depth : 1,
            color : 0xffffff,
            velocity : new THREE.Vector3(0.0, 0.0, 0.0),
            state : paddleStates.STATIONARY,
            startPosition : new THREE.Vector3(0.0, 0.0, -4.0)
        };

        let ball = {
            radius : 0.5,
            color : 0xffff00,
            velocity : new THREE.Vector3(settings.ballSpeed, 0.0, -settings.ballSpeed),
            startPosition : new THREE.Vector3(0.0, 0.0, -9.0),
            segments : {
                width : 16,
                height : 16
            }
        };

        const levelBounds = {
            top : -35.0,
            right : 17.0,
            left : -17.0,
            bottom : 0.0
        };

        const bricks = {
            rows : 11,
            columns : 11,
            distanceFromEdges : 1.0,
            distanceFromTop : 13.0,
            spacing : 0.2,
            color : 0xff00ff,
            depth : 1.0
        };

        const lights = [
            new THREE.AmbientLight(0xffffff, 0.5), 
            new THREE.PointLight(0xffffff, 0.5)
        ];

        // Game
        let renderer = createFullScreenRenderer("game-window", settings);

        let scene = new THREE.Scene();
        let camera = createCamera();
        scene.add(camera);

        paddle.mesh = createMeshAtPosition({
            geometry : new THREE.BoxGeometry(paddle.width, paddle.depth, paddle.height),
            material : new THREE.MeshLambertMaterial({ color : paddle.color })
        }, paddle.startPosition);
        scene.add(paddle.mesh);

        ball.mesh = createMeshAtPosition({
            geometry : new THREE.SphereGeometry(ball.radius, ball.segments.width, ball.segments.height),
            material : new THREE.MeshLambertMaterial({ color: ball.color })
        }, ball.startPosition);
        scene.add(ball.mesh);

        lights.forEach(light => scene.add(light));

        const levelWidth = levelBounds.right - levelBounds.left;
        const brick = {
            width : (levelWidth - 2 * bricks.distanceFromEdges + bricks.spacing * (1 - bricks.columns)) / bricks.columns,
            height : (bricks.distanceFromTop - bricks.distanceFromEdges) / bricks.rows,
            depth : bricks.depth
        };

        let visibleBricks = [];
        for (let row = 0; row < bricks.rows; row += 1) {
            for (let column = 0; column < bricks.columns; column += 1) {
                let position = new THREE.Vector3(
                    levelBounds.left + bricks.distanceFromEdges + column * (brick.width + bricks.spacing) + 0.5 * brick.width,
                    0.0,
                    levelBounds.top + bricks.distanceFromEdges + row * (brick.height + bricks.spacing) + 0.5 * brick.height);
                let mesh = createMeshAtPosition({
                    geometry : new THREE.BoxGeometry(brick.width, brick.depth, brick.height),
                    material : new THREE.MeshLambertMaterial({ color : bricks.color })
                }, position);
                let name = `${row},${column}`;
                mesh.name = name;
                scene.add(mesh);
                visibleBricks.push({
                    position : position,
                    name : name
                });
            }
        }  

        requestAnimationFrame(render);
        function render() {
            // update paddle position
            // ball-level collision
            if ((ball.mesh.position.z - ball.radius < levelBounds.top && ball.velocity.z < 0.0) ||
                (ball.mesh.position.z + ball.radius > levelBounds.bottom && ball.velocity.z > 0.0)) 
            {
                ball.velocity.z *= -1.0;
            }

            if ((ball.mesh.position.x + ball.radius > levelBounds.right && ball.velocity.x > 0.0) ||
                (ball.mesh.position.x - ball.radius < levelBounds.left && ball.velocity.x < 0.0))
            {
                ball.velocity.x *= -1.0;
            }

            resolveBallBlockCollision(ball, paddle.mesh, paddle, function() {});

            // ball-brick collision
            for (let i = 0; i < visibleBricks.length; i += 1) {
                let visibleBrick = visibleBricks[i];
                let isCollided = resolveBallBlockCollision(ball, visibleBrick, brick, function() {
                    let selectedObject = scene.getObjectByName(visibleBrick.name);
                    scene.remove(selectedObject);
                    visibleBricks.splice(i, 1);
                });
                if (isCollided) {
                    break;
                }
            }

            updatePosition(paddle);
            updatePosition(ball);
            renderer.render(scene, camera);
            requestAnimationFrame(render);
        }

        window.addEventListener("resize", makeResizeCallback(camera, renderer), false);
        window.addEventListener("keydown", makeKeyDownCallback(paddle, settings.paddleSpeed), false);
        window.addEventListener("keyup", makeKeyUpCallback(paddle), false);
    }

    window.addEventListener("load", main, false);
})();
<!DOCTYPE html>
<html>
    <head>
        <title>Arkanoid</title>
        <style>
            body {
                padding: 0px;
                margin: 0px;
                overflow: hidden;
            }
        </style>
    </head>
    <body>
        <canvas id="game-window"></canvas>
        https://cdnjs.cloudflare.com/ajax/libs/three.js/95/three.js
    </body>
</html>


Get this bounty!!!

#StackBounty: #javascript #game Arkanoid/Breakout clone with ThreeJS

Bounty: 100

Here’s my attempt at an Arkanoid/Breakout clone using JavaScript with ThreeJS. The feedback I’m looking for is more on the code side; the game itself is a work in progress. You can take it for a spin by running the code snippet below.

(function() {
    "use strict";

    const paddleStates = {
        MOVING_LEFT : 0,
        MOVING_RIGHT : 1,
        STATIONARY : 2
    };

    function createMeshAtPosition(meshProperties, position) {
        let mesh = new THREE.Mesh(meshProperties.geometry, meshProperties.material);
        mesh.position.copy(position);
        return mesh;
    }

    function createFullScreenRenderer(elementId, settings) {
        let renderer = new THREE.WebGLRenderer({
            canvas: document.getElementById(elementId)
        });
        renderer.setPixelRatio(window.devicePixelRatio);
        renderer.setSize(window.innerWidth, window.innerHeight);
        renderer.setClearColor(settings.backgroundColor);
        return renderer;
    }

    function createCamera() {
        let camera = new THREE.PerspectiveCamera(
            90,
            window.innerWidth / window.innerHeight,
            0.1,
            3000);
        camera.position.set(0.0, 10.0, 0.0);
        camera.lookAt(0.0, 0.0, -10.0);
        return camera;
    }

    function makeResizeCallback(camera, renderer) {
        return function() {
            camera.aspect = window.innerWidth / window.innerHeight;
            camera.updateProjectionMatrix();
            renderer.setSize(window.innerWidth, window.innerHeight);
        };
    }

    function makeKeyDownCallback(paddle, speed) {
        return function(event) {
            if (paddle.state === paddleStates.STATIONARY) {
                if (event.key === "ArrowLeft") {
                    paddle.velocity.x = -speed;
                    paddle.state = paddleStates.MOVING_LEFT;
                } else if (event.key === "ArrowRight") {
                    paddle.velocity.x = speed;
                    paddle.state = paddleStates.MOVING_RIGHT;
                }
            }
        };
    }

    function makeKeyUpCallback(paddle) {
        return function(event) {
            if (paddle.state === paddleStates.MOVING_LEFT && event.key === "ArrowLeft") {
                paddle.velocity.x = 0.0;
                paddle.state = paddleStates.STATIONARY;
            } else if (paddle.state === paddleStates.MOVING_RIGHT && event.key === "ArrowRight") {
                paddle.velocity.x = 0.0;
                paddle.state = paddleStates.STATIONARY;
            }
        };
    }

    function updatePosition(gameObject) {
        gameObject.mesh.position.add(gameObject.velocity);
    }

    function resolveBallBlockCollision(ball, blockMesh, blockProperties, callback) {
        if ((ball.mesh.position.z + ball.radius > blockMesh.position.z - blockProperties.height / 2 &&
            (ball.mesh.position.z < blockMesh.position.z)) &&
            (ball.mesh.position.x > blockMesh.position.x - blockProperties.width / 2) &&
            (ball.mesh.position.x < blockMesh.position.x + blockProperties.width / 2) &&
            (ball.velocity.z > 0.0)) 
        {
            ball.velocity.z *= -1.0;
            callback();
            return true;
        }

        if ((ball.mesh.position.z - ball.radius < blockMesh.position.z + blockProperties.height / 2 &&
            (ball.mesh.position.z > blockMesh.position.z)) &&
            (ball.mesh.position.x > blockMesh.position.x - blockProperties.width / 2) &&
            (ball.mesh.position.x < blockMesh.position.x + blockProperties.width / 2) &&
            (ball.velocity.z < 0.0)) 
        {
            ball.velocity.z *= -1.0;
            callback();
            return true;
        }

        if ((ball.mesh.position.x + ball.radius > blockMesh.position.x - blockProperties.width / 2 &&
            (ball.mesh.position.x < blockMesh.position.x)) &&
            (ball.mesh.position.z > blockMesh.position.z - blockProperties.height / 2) &&
            (ball.mesh.position.z < blockMesh.position.z + blockProperties.height / 2) &&
            (ball.velocity.x > 0.0)) 
        {
            ball.velocity.x *= -1.0;
            callback();
            return true;
        }

        if ((ball.mesh.position.x - ball.radius < blockMesh.position.x + blockProperties.width / 2 &&
            (ball.mesh.position.x > blockMesh.position.x)) &&
            (ball.mesh.position.z > blockMesh.position.z - blockProperties.height / 2) &&
            (ball.mesh.position.z < blockMesh.position.z + blockProperties.height / 2) &&
            (ball.velocity.x < 0.0)) 
        {
            ball.velocity.x *= -1.0;
            callback();
            return true;
        }

        return false;
    }

    function main() {
        // Hard-coded "settings"
        let settings = {
            backgroundColor : 0x008888,
            paddleSpeed : 0.3,
            ballSpeed: 0.2
        };

        let paddle = {
            width : 4,
            height : 1,
            depth : 1,
            color : 0xffffff,
            velocity : new THREE.Vector3(0.0, 0.0, 0.0),
            state : paddleStates.STATIONARY,
            startPosition : new THREE.Vector3(0.0, 0.0, -4.0)
        };

        let ball = {
            radius : 0.5,
            color : 0xffff00,
            velocity : new THREE.Vector3(settings.ballSpeed, 0.0, -settings.ballSpeed),
            startPosition : new THREE.Vector3(0.0, 0.0, -9.0),
            segments : {
                width : 16,
                height : 16
            }
        };

        const levelBounds = {
            top : -35.0,
            right : 17.0,
            left : -17.0,
            bottom : 0.0
        };

        const bricks = {
            rows : 11,
            columns : 11,
            distanceFromEdges : 1.0,
            distanceFromTop : 13.0,
            spacing : 0.2,
            color : 0xff00ff,
            depth : 1.0
        };

        const lights = [
            new THREE.AmbientLight(0xffffff, 0.5), 
            new THREE.PointLight(0xffffff, 0.5)
        ];

        // Game
        let renderer = createFullScreenRenderer("game-window", settings);

        let scene = new THREE.Scene();
        let camera = createCamera();
        scene.add(camera);

        paddle.mesh = createMeshAtPosition({
            geometry : new THREE.BoxGeometry(paddle.width, paddle.depth, paddle.height),
            material : new THREE.MeshLambertMaterial({ color : paddle.color })
        }, paddle.startPosition);
        scene.add(paddle.mesh);

        ball.mesh = createMeshAtPosition({
            geometry : new THREE.SphereGeometry(ball.radius, ball.segments.width, ball.segments.height),
            material : new THREE.MeshLambertMaterial({ color: ball.color })
        }, ball.startPosition);
        scene.add(ball.mesh);

        lights.forEach(light => scene.add(light));

        const levelWidth = levelBounds.right - levelBounds.left;
        const brick = {
            width : (levelWidth - 2 * bricks.distanceFromEdges + bricks.spacing * (1 - bricks.columns)) / bricks.columns,
            height : (bricks.distanceFromTop - bricks.distanceFromEdges) / bricks.rows,
            depth : bricks.depth
        };

        let visibleBricks = [];
        for (let row = 0; row < bricks.rows; row += 1) {
            for (let column = 0; column < bricks.columns; column += 1) {
                let position = new THREE.Vector3(
                    levelBounds.left + bricks.distanceFromEdges + column * (brick.width + bricks.spacing) + 0.5 * brick.width,
                    0.0,
                    levelBounds.top + bricks.distanceFromEdges + row * (brick.height + bricks.spacing) + 0.5 * brick.height);
                let mesh = createMeshAtPosition({
                    geometry : new THREE.BoxGeometry(brick.width, brick.depth, brick.height),
                    material : new THREE.MeshLambertMaterial({ color : bricks.color })
                }, position);
                let name = `${row},${column}`;
                mesh.name = name;
                scene.add(mesh);
                visibleBricks.push({
                    position : position,
                    name : name
                });
            }
        }  

        requestAnimationFrame(render);
        function render() {
            // update paddle position
            // ball-level collision
            if ((ball.mesh.position.z - ball.radius < levelBounds.top && ball.velocity.z < 0.0) ||
                (ball.mesh.position.z + ball.radius > levelBounds.bottom && ball.velocity.z > 0.0)) 
            {
                ball.velocity.z *= -1.0;
            }

            if ((ball.mesh.position.x + ball.radius > levelBounds.right && ball.velocity.x > 0.0) ||
                (ball.mesh.position.x - ball.radius < levelBounds.left && ball.velocity.x < 0.0))
            {
                ball.velocity.x *= -1.0;
            }

            resolveBallBlockCollision(ball, paddle.mesh, paddle, function() {});

            // ball-brick collision
            for (let i = 0; i < visibleBricks.length; i += 1) {
                let visibleBrick = visibleBricks[i];
                let isCollided = resolveBallBlockCollision(ball, visibleBrick, brick, function() {
                    let selectedObject = scene.getObjectByName(visibleBrick.name);
                    scene.remove(selectedObject);
                    visibleBricks.splice(i, 1);
                });
                if (isCollided) {
                    break;
                }
            }

            updatePosition(paddle);
            updatePosition(ball);
            renderer.render(scene, camera);
            requestAnimationFrame(render);
        }

        window.addEventListener("resize", makeResizeCallback(camera, renderer), false);
        window.addEventListener("keydown", makeKeyDownCallback(paddle, settings.paddleSpeed), false);
        window.addEventListener("keyup", makeKeyUpCallback(paddle), false);
    }

    window.addEventListener("load", main, false);
})();
<!DOCTYPE html>
<html>
    <head>
        <title>Arkanoid</title>
        <style>
            body {
                padding: 0px;
                margin: 0px;
                overflow: hidden;
            }
        </style>
    </head>
    <body>
        <canvas id="game-window"></canvas>
        https://cdnjs.cloudflare.com/ajax/libs/three.js/95/three.js
    </body>
</html>


Get this bounty!!!

#StackBounty: #c++ #game #gui #c++17 Hunt the Wumpus GUI (FLTK)

Bounty: 50

I used the code from the text based hunt the wumpus game discussed here: Text based game “Hunt the Wumpus” Version 3 to create a gui Version based on excercises from PPP by Stroustrup.

enter image description here
enter image description here

For the GUI i used FLTK because the book is based on it. I used it with MSVS2017. I got FLTK to run with this tutorial:
https://bumpyroadtocode.com/2017/08/05/how-to-install-and-use-fltk-1-3-4-in-visual-studio-2017-complete-guide/

Also Stroustrup provides some basic GUI library which wraps the FLTK functions:
http://www.stroustrup.com/Programming/PPP2code/

I modified the headers sligtly by removing the std_lib_facilities.h because it includes many unessary stuff and basically hides which c++ standard headers are included.

Unfortunately the source code is a bit to big to get posted here, espcially the wumpus_window.cpp file. I do some repetive stuff there which i think is the reason i have to much code.

Here is the class declaration for the Window and the Rooms:

wumpus_window.h

    #ifndef WUMPUS_WINDOW_GUARD_060820182207
#define WUMPUS_WINDOW_GUARD_060820182207

#include "Window.h"
#include "GUI.h"
#include "Point.h"

#include <array>

namespace wumpus {

    using Room_number = int;
    using Arrows = int;

    class Wumpus_window : public Graph_lib::Window {
    public:
        Wumpus_window();
    private:
        static const Point window_offset_xy;

        static constexpr auto window_size_x = 1600;
        static constexpr auto window_size_y = 1000;

        static constexpr auto count_of_pits = 2;
        static constexpr auto count_of_bats = 2;
        static constexpr auto count_of_rooms = 20;

        static constexpr auto count_of_arrows = 5;

        Arrows arrows = count_of_arrows;
        int wins = 0;
        int losts = 0;

        class Room : public Graph_lib::Rectangle {
        private:
            static constexpr auto count_of_neighbours = 3;
        public:
            Room(Room* first, Room* second, Room* third, Point xy, int len);

            std::array <Room*, count_of_neighbours> get_neigbours() const { return m_neighbours; }

            void set_wumpus()                       { m_has_wumpus = true; }
            void reset_wumpus()                     { m_has_wumpus = false; }
            bool has_wumpus()               const   { return m_has_wumpus; }

            void set_bat()                          { m_has_bat = true; }
            void reset_bat()                        { m_has_bat = false; }
            bool has_bat()                  const   { return m_has_bat; }

            void set_pit()                          { m_has_pit = true; }
            void reset_pit()                        { m_has_pit = false; }
            bool has_pit()                  const   { return m_has_pit; }

            void set_player();
            void reset_player();
            bool has_player()               const   { return m_has_player; }

            void reset_player_was_here()            { m_player_was_here = false; }
            bool player_was_here()          const   { return m_player_was_here; }

            void set_guess_wumpus();
            void reset_guess_wumpus();
            bool guess_wumpus()             const { return m_guess_wumpus; }

            void set_guess_bat();
            void reset_guess_bat();
            bool guess_bat()                const { return m_guess_bat; }

            void set_guess_pit();
            void reset_guess_pit();
            bool guess_pit()                const { return m_guess_pit; }

            Room_number get_room_number()   const { return m_room_number; }
            void set_room_number(Room_number room_number) { m_room_number = room_number; }

            void connect_to_neigbour_rooms_with_lines();

            void hide_all();

            Point btn_move_xy()                 const   { return Point{ point(0).x,point(0).y + height() }; }
            int btn_move_size_x()               const   { return width(); }
            int btn_move_size_y()               const   { return height() / 3; }
            const char* btn_move_label()        const   { return "move"; }

            Point btn_guess_wumpus_xy()         const   { return Point{ point(0).x + width(), point(0).y }; }
            int btn_guess_wumpus_size_x()       const   { return width() / 3; }
            int btn_guess_wumpus_size_y()       const   { return height() / 3; }
            const char* btn_guess_wumpus_label()const   { return "w"; }

            Point btn_guess_bat_xy()            const   { return Point{ point(0).x + width(), point(0).y + height() /3 }; }
            int btn_guess_bat_size_x()          const   { return width() / 3; }
            int btn_guess_bat_size_y()          const   { return height() / 3; }
            const char* btn_guess_bat_label()   const   { return "b"; }

            Point btn_guess_pit_xy()            const   { return Point{ point(0).x + width(), point(0).y + (height() /3) * 2 }; }
            int btn_guess_pit_size_x()          const   { return width() / 3; }
            int btn_guess_pit_size_y()          const   { return height() / 3; }
            const char* btn_guess_pit_label()   const   { return "p"; }
        private:
            std::array <Room*, count_of_neighbours> m_neighbours;

            bool m_has_wumpus{ false };
            bool m_has_bat{ false };
            bool m_has_pit{ false };
            bool m_has_player{ false };
            bool m_player_was_here{ false };

            bool m_guess_wumpus{ false };
            bool m_guess_bat{ false };
            bool m_guess_pit{ false };

            Room_number m_room_number{ 0 };

            static constexpr auto room_color                            = Graph_lib::Color::black;
            static constexpr auto room_fill_color_neigbour_room         = Graph_lib::Color::yellow;
            static constexpr auto room_fill_color_player_room           = Graph_lib::Color::blue;
            static constexpr auto room_fill_color_player_was_here_room  = Graph_lib::Color::green;

            Point m_center_xy;

            // Dynamically is necessary here because connections can only be drawn after the rooms are connected 
            // with their neigbours
            Graph_lib::Vector_ref <Graph_lib::Line> line_connections;

            void draw_lines() const override;

            static constexpr auto rectangle_guess_wumpus_color = Graph_lib::Color::black;
            static constexpr auto rectangle_guess_wumpus_fill_color = Graph_lib::Color::dark_red;
            Graph_lib::Rectangle rectangle_guess_wumpus;

            static constexpr auto rectangle_guess_bat_color = Graph_lib::Color::black;
            static constexpr auto rectangle_guess_bat_fill_color = Graph_lib::Color::dark_cyan;
            Graph_lib::Rectangle rectangle_guess_bat;

            static constexpr auto rectangle_guess_pit_color = Graph_lib::Color::black;
            static constexpr auto rectangle_guess_pit_fill_color = Graph_lib::Color::dark_yellow;
            Graph_lib::Rectangle rectangle_guess_pit;

            Graph_lib::Text txt_room_number;
        };

        static constexpr auto room_size_xy = 70;
        static const std::array<Point, 20> room_xy;

        std::array<Room, count_of_rooms> rooms
        {
            {
                { &rooms[1]     ,&rooms[4],     &rooms[19], room_xy[0], room_size_xy },
                { &rooms[0]     ,&rooms[2],     &rooms[17], room_xy[1], room_size_xy },
                { &rooms[1]     ,&rooms[3],     &rooms[15], room_xy[2], room_size_xy }, 
                { &rooms[2]     ,&rooms[4],     &rooms[13], room_xy[3], room_size_xy },
                { &rooms[0]     ,&rooms[3],     &rooms[5],  room_xy[4], room_size_xy },
                { &rooms[4]     ,&rooms[6],     &rooms[12], room_xy[5], room_size_xy },
                { &rooms[5]     ,&rooms[7],     &rooms[19], room_xy[6], room_size_xy },
                { &rooms[6]     ,&rooms[8],     &rooms[11], room_xy[7], room_size_xy },
                { &rooms[7]     ,&rooms[9],     &rooms[18], room_xy[8], room_size_xy }, 
                { &rooms[8]     ,&rooms[10],    &rooms[16], room_xy[9], room_size_xy },
                { &rooms[9]     ,&rooms[11],    &rooms[14], room_xy[10],room_size_xy },
                { &rooms[7]     ,&rooms[10],    &rooms[12], room_xy[11],room_size_xy },
                { &rooms[5]     ,&rooms[11],    &rooms[13], room_xy[12],room_size_xy },
                { &rooms[3]     ,&rooms[12],    &rooms[14], room_xy[13],room_size_xy },
                { &rooms[10]    ,&rooms[13],    &rooms[15], room_xy[14],room_size_xy },
                { &rooms[2]     ,&rooms[14],    &rooms[16], room_xy[15],room_size_xy },
                { &rooms[9]     ,&rooms[15],    &rooms[17], room_xy[16],room_size_xy },
                { &rooms[1]     ,&rooms[16],    &rooms[18], room_xy[17],room_size_xy },
                { &rooms[8]     ,&rooms[17],    &rooms[19], room_xy[18],room_size_xy },
                { &rooms[0]     ,&rooms[6],     &rooms[18], room_xy[19],room_size_xy },
            }
        };

        static const std::vector<std::string>   txt_instructions_txt;
        static const Point                      txt_instructions_xy;
        static constexpr auto                   txt_instructions_offset_y = 28;
        static constexpr auto                   txt_instructions_font_size = 20;
        static constexpr auto                   txt_instructions_color  = Graph_lib::Color::black;
        Graph_lib::Vector_ref <Graph_lib::Text> txt_instructions;
        void print_game_instructions();

        static const Point      btn_start_again_xy;
        static constexpr auto   btn_start_again_size_x = 150;
        static constexpr auto   btn_start_again_size_y = 75;
        static constexpr auto   btn_start_again_label = "Start";
        Graph_lib::Button       btn_start;
        void btn_start_event();
        void init_window();
        void init_dungeon();

        static const Point      txt_indicate_wumpus_is_near_xy;
        static constexpr auto   txt_indicate_wumpus_is_near_font_size = 60;
        static constexpr auto   txt_indicate_wumpus_is_near_txt = "I smell the wumpus";
        static constexpr auto   txt_indicate_wumpus_is_near_color = Graph_lib::Color::dark_red;
        Graph_lib::Text         txt_indicate_wumpus_is_near;

        static const Point      txt_indicate_bat_is_near_xy;
        static constexpr auto   txt_indicate_bat_is_near_font_size = 60;
        static constexpr auto   txt_indicate_bat_is_near_txt = "I hear a bat";
        static constexpr auto   txt_indicate_bat_is_near_color = Graph_lib::Color::dark_cyan;
        Graph_lib::Text         txt_indicate_bat_is_near;

        static const Point      txt_indicate_pit_is_near_xy;
        static constexpr auto   txt_indicate_pit_is_near_font_size = 60;
        static constexpr auto   txt_indicate_pit_is_near_txt = "I feel a breeze";
        static constexpr auto   txt_indicate_pit_is_near_color = Graph_lib::Color::dark_yellow;
        Graph_lib::Text         txt_indicate_pit_is_near;

        static const Point      txt_indicate_dragged_by_bat_xy;
        static constexpr auto   txt_indicate_dragged_by_bat_font_size = 30;
        static constexpr auto   txt_indicate_dragged_by_bat_txt = "Gigantic bat appeared and dragged you to room ";
        static constexpr auto   txt_indicate_dragged_by_bat_color = Graph_lib::Color::red;
        Graph_lib::Text         txt_indicate_dragged_by_bat;

        void indicate_hazards();

        Graph_lib::Vector_ref <Graph_lib::Button> btn_move;
        void btn_move_0_event() { move_player(rooms[0]); }
        void btn_move_1_event() { move_player(rooms[1]); }
        void btn_move_2_event() { move_player(rooms[2]); }
        void btn_move_3_event() { move_player(rooms[3]); }
        void btn_move_4_event() { move_player(rooms[4]); }
        void btn_move_5_event() { move_player(rooms[5]); }
        void btn_move_6_event() { move_player(rooms[6]); }
        void btn_move_7_event() { move_player(rooms[7]); }
        void btn_move_8_event() { move_player(rooms[8]); }
        void btn_move_9_event() { move_player(rooms[9]); }
        void btn_move_10_event(){ move_player(rooms[10]); }
        void btn_move_11_event(){ move_player(rooms[11]); }
        void btn_move_12_event(){ move_player(rooms[12]); }
        void btn_move_13_event(){ move_player(rooms[13]); }
        void btn_move_14_event(){ move_player(rooms[14]); }
        void btn_move_15_event(){ move_player(rooms[15]); }
        void btn_move_16_event(){ move_player(rooms[16]); }
        void btn_move_17_event(){ move_player(rooms[17]); }
        void btn_move_18_event(){ move_player(rooms[18]); }
        void btn_move_19_event(){ move_player(rooms[19]); }

        void init_btns_move_to_room();
        void show_btns_move_to_room();

        void move_player(Room& room);

        Graph_lib::Vector_ref<Graph_lib::Button> btn_guess_wumpus;
        void btn_guess_wumpus_in_room_0_event() { guess_wumpus(rooms[0]); }
        void btn_guess_wumpus_in_room_1_event() { guess_wumpus(rooms[1]); }
        void btn_guess_wumpus_in_room_2_event() { guess_wumpus(rooms[2]); }
        void btn_guess_wumpus_in_room_3_event() { guess_wumpus(rooms[3]); }
        void btn_guess_wumpus_in_room_4_event() { guess_wumpus(rooms[4]); }
        void btn_guess_wumpus_in_room_5_event() { guess_wumpus(rooms[5]); }
        void btn_guess_wumpus_in_room_6_event() { guess_wumpus(rooms[6]); }
        void btn_guess_wumpus_in_room_7_event() { guess_wumpus(rooms[7]); }
        void btn_guess_wumpus_in_room_8_event() { guess_wumpus(rooms[8]); }
        void btn_guess_wumpus_in_room_9_event() { guess_wumpus(rooms[9]); }
        void btn_guess_wumpus_in_room_10_event(){ guess_wumpus(rooms[10]); }
        void btn_guess_wumpus_in_room_11_event(){ guess_wumpus(rooms[11]); }
        void btn_guess_wumpus_in_room_12_event(){ guess_wumpus(rooms[12]); }
        void btn_guess_wumpus_in_room_13_event(){ guess_wumpus(rooms[13]); }
        void btn_guess_wumpus_in_room_14_event(){ guess_wumpus(rooms[14]); }
        void btn_guess_wumpus_in_room_15_event(){ guess_wumpus(rooms[15]); }
        void btn_guess_wumpus_in_room_16_event(){ guess_wumpus(rooms[16]); }
        void btn_guess_wumpus_in_room_17_event(){ guess_wumpus(rooms[17]); }
        void btn_guess_wumpus_in_room_18_event(){ guess_wumpus(rooms[18]); }
        void btn_guess_wumpus_in_room_19_event(){ guess_wumpus(rooms[19]); }

        void init_btns_guess_wumpus();
        void show_btns_guess_wumpus_in_room();

        void guess_wumpus(Room& room);

        Graph_lib::Vector_ref<Graph_lib::Button> btn_guess_bat;
        void btn_guess_bat_in_room_0_event(){ guess_bat(rooms[0]); }
        void btn_guess_bat_in_room_1_event(){ guess_bat(rooms[1]); }
        void btn_guess_bat_in_room_2_event(){ guess_bat(rooms[2]); }
        void btn_guess_bat_in_room_3_event(){ guess_bat(rooms[3]); }
        void btn_guess_bat_in_room_4_event(){ guess_bat(rooms[4]); }
        void btn_guess_bat_in_room_5_event(){ guess_bat(rooms[5]); }
        void btn_guess_bat_in_room_6_event(){ guess_bat(rooms[6]); }
        void btn_guess_bat_in_room_7_event(){ guess_bat(rooms[7]); }
        void btn_guess_bat_in_room_8_event(){ guess_bat(rooms[8]); }
        void btn_guess_bat_in_room_9_event(){ guess_bat(rooms[9]); }
        void btn_guess_bat_in_room_10_event(){ guess_bat(rooms[10]); }
        void btn_guess_bat_in_room_11_event(){ guess_bat(rooms[11]); }
        void btn_guess_bat_in_room_12_event(){ guess_bat(rooms[12]); }
        void btn_guess_bat_in_room_13_event(){ guess_bat(rooms[13]); }
        void btn_guess_bat_in_room_14_event(){ guess_bat(rooms[14]); }
        void btn_guess_bat_in_room_15_event(){ guess_bat(rooms[15]); }
        void btn_guess_bat_in_room_16_event(){ guess_bat(rooms[16]); }
        void btn_guess_bat_in_room_17_event(){ guess_bat(rooms[17]); }
        void btn_guess_bat_in_room_18_event(){ guess_bat(rooms[18]); }
        void btn_guess_bat_in_room_19_event(){ guess_bat(rooms[19]); }

        void init_btns_guess_bat();
        void show_btns_guess_bat_in_room();

        void guess_bat(Room& room);


        Graph_lib::Vector_ref<Graph_lib::Button> btn_guess_pit;
        void btn_guess_pit_in_room_0_event(){ guess_pit(rooms[0]); }
        void btn_guess_pit_in_room_1_event(){ guess_pit(rooms[1]); }
        void btn_guess_pit_in_room_2_event(){ guess_pit(rooms[2]); }
        void btn_guess_pit_in_room_3_event(){ guess_pit(rooms[3]); }
        void btn_guess_pit_in_room_4_event(){ guess_pit(rooms[4]); }
        void btn_guess_pit_in_room_5_event(){ guess_pit(rooms[5]); }
        void btn_guess_pit_in_room_6_event(){ guess_pit(rooms[6]); }
        void btn_guess_pit_in_room_7_event(){ guess_pit(rooms[7]); }
        void btn_guess_pit_in_room_8_event(){ guess_pit(rooms[8]); }
        void btn_guess_pit_in_room_9_event(){ guess_pit(rooms[9]); }
        void btn_guess_pit_in_room_10_event(){ guess_pit(rooms[10]); }
        void btn_guess_pit_in_room_11_event(){ guess_pit(rooms[11]); }
        void btn_guess_pit_in_room_12_event(){ guess_pit(rooms[12]); }
        void btn_guess_pit_in_room_13_event(){ guess_pit(rooms[13]); }
        void btn_guess_pit_in_room_14_event(){ guess_pit(rooms[14]); }
        void btn_guess_pit_in_room_15_event(){ guess_pit(rooms[15]); }
        void btn_guess_pit_in_room_16_event(){ guess_pit(rooms[16]); }
        void btn_guess_pit_in_room_17_event(){ guess_pit(rooms[17]); }
        void btn_guess_pit_in_room_18_event(){ guess_pit(rooms[18]); }
        void btn_guess_pit_in_room_19_event(){ guess_pit(rooms[19]); }

        void init_btns_guess_pit();
        void show_btns_guess_pit_in_room();

        void guess_pit(Room& room);

        static const Point      out_remaining_arrows_xy;
        static constexpr auto   out_remaining_arrows_size_x = 60;
        static constexpr auto   out_remaining_arrows_size_y = 60;
        static constexpr auto   out_remaining_arrows_label = "Arrows";
        static constexpr auto   out_remaining_arrows_label_size = 60;
        static constexpr auto   out_remaining_arrows_out_size = 60;
        Graph_lib::Out_box      out_remaining_arrows;

        static const Point      in_arrow_target_rooms_xy;
        static constexpr auto   in_arrow_target_rooms_size_x = 250;
        static constexpr auto   in_arrow_target_rooms_size_y = 60;
        static constexpr auto   in_arrow_target_rooms_label = "Target";
        static constexpr auto   in_arrow_target_rooms_label_size = 60;
        static constexpr auto   in_arrow_target_rooms_in_size = 60;
        Graph_lib::In_box       in_arrow_target_rooms;

        static const Point      txt_invalid_input_xy;
        static constexpr auto   txt_invalid_input_font_size = 60;
        static constexpr auto   txt_invalid_input_txt = "Invalid Input";
        static constexpr auto   txt_invalid_input_color = Graph_lib::Color::black;
        Graph_lib::Text         txt_invalid_input;

        static const Point      btn_shoot_arrow_xy;
        static constexpr auto   btn_shoot_arrow_size_x = 100;
        static constexpr auto   btn_shoot_arrow_size_y = 60;
        static constexpr auto   btn_shoot_arrow_label = "Shoot!!!";
        Graph_lib::Button       btn_shoot_arrow;

        void btn_shoot_arrow_event();
        void shoot_arrow(const std::vector<Room_number>& target_rooms);
        void move_wumpus();

        static const Point      txt_game_over_1_row_xy;
        static const Point      txt_game_over_2_row_xy;
        static constexpr auto   txt_game_over_font_size = 100;
        static constexpr auto   txt_game_over_txt_win           = "YOU WON";
        static constexpr auto   txt_game_over_txt_win_wumpus    = "You killed Wumpus in room ";
        static constexpr auto   txt_game_over_txt_color_win     = Graph_lib::Color::dark_green;
        static constexpr auto   txt_game_over_txt_lose          = "YOU LOST";
        static constexpr auto   txt_game_over_txt_lose_wumpus       = "You got eaten by the Wumpus!";
        static constexpr auto   txt_game_over_txt_lose_wumpus_move= "The Wumpus enters your room!";
        static constexpr auto   txt_game_over_txt_lose_pit      = "You felt into a bottomless pit!";
        static constexpr auto   txt_game_over_txt_lose_arrow        = "You got killed by youre own arrow";
        static constexpr auto   txt_game_over_txt_lose_no_arrows    = "You ran out of arrows";
        static constexpr auto   txt_game_over_txt_color_lose        = Graph_lib::Color::red;
        Graph_lib::Text         txt_game_over_1_row;
        Graph_lib::Text         txt_game_over_2_row;

        void game_over(const std::string& txt_first_row, const std::string& txt_second_row, const Graph_lib::Color& txt_color);


        static const Point      btn_try_again_xy;
        static constexpr auto   btn_try_again_size_x = window_size_x / 3;
        static constexpr auto   btn_try_again_size_y = (window_size_y / 3);
        static constexpr auto   btn_try_again_label = "Try again";
        Graph_lib::Button       btn_try_again;
        void btn_try_again_event();

        static const Point      out_wins_xy;
        static constexpr auto   out_wins_size_x = 300;
        static constexpr auto   out_wins_size_y = 60;
        static constexpr auto   out_wins_label = "Won:";
        static constexpr auto   out_wins_label_size = 60;
        static constexpr auto   out_wins_out_size = 60;
        Graph_lib::Out_box      out_wins;

        static const Point      out_losts_xy;
        static constexpr auto   out_losts_size_x = 300;
        static constexpr auto   out_losts_size_y = 60;
        static constexpr auto   out_losts_label = "Lost:";
        static constexpr auto   out_losts_label_size = 60;
        static constexpr auto   out_losts_out_size = 60;
        Graph_lib::Out_box      out_losts;

        void show_state_of_dungeon();   // for debug only. shows in console which rooms are connected with which number
    };

    std::vector<Room_number> get_target_rooms(const std::string& input);
    int hunt_the_wumpus();
    int get_random(int min, int max);
}

#endif

What bothers me here the most is the handling of the Buttons.
I have 4 Buttons for each Room:

-guess room has a bat (draws a small Rectangle in the room indicate there could be a bat)

-guess room has the wumpus

-guess room has a pit

-move -> pushing trys to move the player into the room.

I tryed to include these Buttons directly into the class Room but i could only manage to put in the coordinates and the sizes.

If i create the Buttons inside the room and assign a callback function from inside the Room class the application crashes because i violate that Wumpus_window and not Room is responsible for draw the widgets (the small rectangles get displayed/ hided in the callbacks).

So currently i have these 80 numbered callback methods in Wumpus_window. Which would be alot less repetive in the Room class. With this initing the buttons is a lot of repetive work which cant be looped (e.g. void init_btns_guess_pit())

The complete source code can be found here:
https://gist.github.com/sandro4912/e603a6ea5d425b299701565098be11fc

Feel free to comment anything else you find suspicious to Wumpus_window.h or Wumpus_window.cpp. The other files are the support files from Stroustrup slightly modified.


Get this bounty!!!

#StackBounty: #c++ #game #c++17 C++ Inventory System

Bounty: 50

I have recently been working a lot on a project for a system that represents an inventory and item usage in a game, with one of the important features is that it should be reusable in multiple projects. I think I managed to do this quite well, keeping the required interface as minimum as possible. I have come to a point where I think the project is finished, but I would like to get feedback on where I can still improve the code. It’s really long, so I’m not sure if I should post everything here. I will just post the main Inventory header here, and the rest of the code can be found on github. If I do need to copy all code here, let me know.

Inventory.h

#pragma once

#include "IItem.h"
#include "ItemDispatcher.h"
#include <memory>
#include <functional>
#include <string>
#include <unordered_map>
#include <string_view>
#include <type_traits>
#include <utility>



template<unsigned int MAX_SIZE, typename GameObjTy = temp::GameObject, typename ItemTy = IItem>
class Inventory
{
private:

    class Traits //class to simulate namespace inside class
    {
    public:

        /*HasUseMethod type trait*/

        template<typename _Ty, typename = std::void_t<>>
        struct HasUseMethodHelper : std::false_type 
        {
        };

        template<typename _Ty>
        struct HasUseMethodHelper<_Ty, std::void_t<decltype(std::declval<_Ty>().use(std::declval<ItemDispatcher<GameObjTy>&>()))>> : std::true_type
        {
        };

        template<typename _Ty>
        struct HasUseMethodT : HasUseMethodHelper<_Ty>::type
        {
        };

        template<typename _Ty> using HasUseMethod = typename HasUseMethodT<_Ty>::type;
        template<typename _Ty> static constexpr bool HasUseMethodV = HasUseMethod<_Ty>::value;

        /*HasEquippabmeMethod type trait*/

        template<typename _Ty, typename = std::void_t<>>
        struct HasEquippableMethodHelper : std::false_type
        {
        };

        template<typename _Ty>
        struct HasEquippableMethodHelper<_Ty, 
            std::void_t<decltype(std::is_same_v<decltype(std::declval<_Ty>().equippable()), bool >)>> : std::true_type
        {
        };

        template<typename _Ty>
        struct HasEquippableMethodT : HasEquippableMethodHelper<_Ty>::type
        {
        };

        template<typename _Ty> using HasEquippableMethod = typename HasEquippableMethodT<_Ty>::type;
        template<typename _Ty> static constexpr bool HasEquippableMethodV = HasEquippableMethod<_Ty>::value;

        /*HasIsEquipped type trait*/

        template<typename _Ty, typename = std::void_t<>>
        struct HasIsEquippedMethodHelper : std::false_type
        {
        };

        template<typename _Ty>
        struct HasIsEquippedMethodHelper<_Ty, 
            std::void_t<decltype(std::is_same_v<decltype(std::declval<_Ty>().is_equipped()), bool >)>> : std::true_type
        {
        };

        template<typename _Ty>
        struct HasIsEquippedMethodT : HasIsEquippedMethodHelper<_Ty>::type
        {
        };

        template<typename _Ty> using HasIsEquippedMethod = typename HasIsEquippedMethodT<_Ty>::type;
        template<typename _Ty> static constexpr bool HasIsEquippedMethodV = HasIsEquippedMethod<_Ty>::value;

        /*HasSetEquip type trait*/

        template<typename _Ty, typename = std::void_t<>>
        struct HasSetEquipMethodHelper : std::false_type
        {
        };

        template<typename _Ty>
        struct HasSetEquipMethodHelper<_Ty, 
            std::void_t<decltype(std::is_same_v<decltype(std::declval<_Ty>().set_equip(std::declval<bool>())), void >)>> 
                : std::true_type
        {
        };

        template<typename _Ty>
        struct HasSetEquipMethodT : HasSetEquipMethodHelper<_Ty>::type
        {
        };

        template<typename _Ty> using HasSetEquipMethod = typename HasSetEquipMethodT<_Ty>::type;
        template<typename _Ty> static constexpr bool HasSetEquipMethodV = HasSetEquipMethod<_Ty>::value;

        /*HasUnequipMethod type trait*/

        template<typename _Ty, typename = std::void_t<>>
        struct HasUnequipMethodHelper : std::false_type
        {
        };

        template<typename _Ty>
        struct HasUnequipMethodHelper<_Ty, 
            std::void_t<decltype(std::is_same_v<decltype(std::declval<_Ty>().unequip(std::declval<GameObjTy*>())), void >)>> 
                : std::true_type
        {
        };

        template<typename _Ty>
        struct HasUnequipMethodT : HasUnequipMethodHelper<_Ty>::type
        {
        };

        template<typename _Ty> using HasUnequipMethod = typename HasUnequipMethodT<_Ty>::type;
        template<typename _Ty> static constexpr bool HasUnequipMethodV = HasUnequipMethod<_Ty>::value;

        /*HasReusableMethod type trait*/

        template<typename _Ty, typename = std::void_t<>>
        struct HasReusableMethodHelper : std::false_type
        {
        };

        template<typename _Ty>
        struct HasReusableMethodHelper<_Ty, 
            std::void_t<decltype(std::is_same_v<decltype(std::declval<_Ty>().reusable()), bool >)>> 
                : std::true_type
        {
        };

        template<typename _Ty>
        struct HasReusableMethodT : HasReusableMethodHelper<_Ty>::type
        {
        };

        template<typename _Ty> using HasReusableMethod = typename HasReusableMethodT<_Ty>::type;
        template<typename _Ty> static constexpr bool HasReusableMethodV = HasReusableMethod<_Ty>::value;

        template<typename _Ty, typename = std::void_t<>>
        struct HasStackableMethodHelper : std::false_type
        {
        };

        template<typename _Ty>
        struct HasStackableMethodHelper<_Ty, 
            std::void_t<decltype(std::is_same_v<decltype(std::declval<_Ty>().stackable()), bool>)>>
                : std::true_type
        {
        };

        template<typename _Ty>
        struct HasStackableMethodT : HasStackableMethodHelper<_Ty>::type
        {
        };

        template<typename _Ty> using HasStackableMethod = typename HasStackableMethodT<_Ty>::type;
        template<typename _Ty> static constexpr bool HasStackableMethodV = HasStackableMethod<_Ty>::value;

        template<typename _Ty>
        struct IsValidItemT
        {
            static constexpr bool value =
                HasEquippableMethodV<_Ty>
                && HasUseMethodV<_Ty>
                && HasIsEquippedMethodV<_Ty>
                && HasSetEquipMethodV<_Ty>
                && HasEquippableMethodV<_Ty>
                && HasReusableMethodV<_Ty>
                && HasStackableMethodV<_Ty>;
        };

        template<typename _Ty> using IsValidItem = typename IsValidItemT<_Ty>::type;
        template<typename _Ty> static constexpr bool IsValidItemV = IsValidItemT<_Ty>::value;
    };

public:
    static_assert(Traits::IsValidItemV<ItemTy>, "Item type is invalid. It should provide methods listed in documentation");

    class Exception
    {
    private:
        std::string msg;
    public:
        explicit inline Exception(std::string_view error) : msg {error} {}
        inline std::string_view what() { return msg; }
    };

    using game_object_type = GameObjTy; 
    using item_type = ItemTy; 
    using item_pointer = std::unique_ptr<item_type>;

    using game_object_pointer = game_object_type*;
    using inventory_type = std::unordered_map<std::string, std::pair<item_pointer, unsigned int>>;
    using iterator = typename inventory_type::iterator;
    using const_iterator = typename inventory_type::const_iterator;
    using size_type = typename inventory_type::size_type;

    explicit Inventory(game_object_pointer owner);
    Inventory(Inventory const& other) = delete;
    Inventory(Inventory&& other);

    Inventory& operator=(Inventory const& other) = delete;
    Inventory& operator=(Inventory&& other);

    inventory_type const& contents() const;
    inventory_type& contents();

    /*Adds a new item, stacked on top of an item with the same ID. The id parameter will be used to access the item*/
    template<typename ItemT>
    void addItem(std::string_view id);

    /*constructs a new item and adds it to the inventory. The id parameter will be used to access the item*/
    template<typename ItemT, typename... Args>
    void emplaceItem(std::string_view id, Args... args);

    void useItem(std::string_view name, game_object_pointer target = nullptr);

    /*The iterators invalidate when a new Item is added to the inventory*/
    iterator getItem(std::string_view name);
    /*The iterators invalidate when a new Item is added to the inventory*/
    const_iterator getItem(std::string_view name) const;

    void removeItem(std::string_view name);

    iterator begin();
    iterator end();

    const_iterator cbegin() const;
    const_iterator cend() const;

    size_type max_size() const;
    size_type size() const;

    /*Merges inventory A with this inventory. Leaves other empty, unless this inventory is full, in which case the leftover
      elements will be deleted*/ //#TODO: leftover elements are left in old inventory?
    template<unsigned int N>
    void merge(Inventory<N, GameObjTy, ItemTy>& other);

    template<unsigned int N>
    bool merge_fits(Inventory<N, GameObjTy, ItemTy> const& other);

    /*Transfers item with name parameter into the inventory specified in destination, unless destination does not have enough
     *space left*/
    template<unsigned int N>
    void transfer(Inventory<N, GameObjTy, ItemTy>& destination, std::string_view name);

    bool empty() const;
    bool full() const;

    void setOwner(game_object_pointer owner);
    game_object_pointer getOwner() const;

    void clear();
    unsigned int getItemCount(std::string_view id) const;

private:
    size_type m_size = 0;

    inventory_type m_items { MAX_SIZE };
    game_object_pointer m_owner { nullptr };

    //these functions are private so you cannot accidentally pass an invalid iterator to one of these, causing undefined behavior

    void useItem(iterator pos, game_object_pointer target = nullptr); 
    void removeItem(iterator pos);

    inline Exception InventoryFullException() const { return Exception {"Inventory is full"}; }
    inline Exception InvalidItemTypeException() const { return Exception {"Item type must be derived from Inventory::ItemTy, which defaults to IItem"}; }
    inline Exception InvalidItemException() const { return Exception { "Invalid item name" }; }
    inline Exception InvalidStackException() const { return Exception {"Tried to stack a non-stackable item"}; }
    inline Exception InvalidIDException() const { return Exception {"ID not found in inventory"}; }
};

template<unsigned int MAX_SIZE, typename GameObjTy, typename ItemTy>
Inventory<MAX_SIZE, GameObjTy, ItemTy>::Inventory(game_object_pointer owner) : m_owner(owner)
{

}

template<unsigned int MAX_SIZE, typename GameObjTy, typename ItemTy>
Inventory<MAX_SIZE, GameObjTy, ItemTy>::Inventory(Inventory&& other) : m_owner(std::move(other.m_owner)), m_items(std::move(other.m_items)), m_size(other.m_size)
{
    other.m_owner = nullptr;
    other.m_items = inventory_type {};
    other.m_size = 0;
}

template<unsigned int MAX_SIZE, typename GameObjTy, typename ItemTy>
Inventory<MAX_SIZE, GameObjTy, ItemTy>& Inventory<MAX_SIZE, GameObjTy, ItemTy>::operator=(Inventory<MAX_SIZE, GameObjTy, ItemTy>&& other)
{
    // #WARNING: Self assignment check is missing
    m_owner = other.m_owner;
    m_items = std::move(other.m_items);
    m_size = other.m_size;

    other.m_owner = nullptr;
    other.m_items = inventory_type {};
    other.m_size = 0;

    return *this;
}

template<unsigned int MAX_SIZE, typename GameObjTy, typename ItemTy>
typename Inventory<MAX_SIZE, GameObjTy, ItemTy>::inventory_type const& Inventory<MAX_SIZE, GameObjTy, ItemTy>::contents() const
{
    return m_items;
}

template<unsigned int MAX_SIZE, typename GameObjTy, typename ItemTy>
typename Inventory<MAX_SIZE, GameObjTy, ItemTy>::inventory_type& Inventory<MAX_SIZE, GameObjTy, ItemTy>::contents()
{
    return m_items;
}

template<unsigned int MAX_SIZE, typename GameObjTy, typename ItemTy>
typename Inventory<MAX_SIZE, GameObjTy, ItemTy>::iterator Inventory<MAX_SIZE, GameObjTy, ItemTy>::begin() 
{ 
    return m_items.begin(); 
}

template<unsigned int MAX_SIZE, typename GameObjTy, typename ItemTy>
typename Inventory<MAX_SIZE, GameObjTy, ItemTy>::iterator Inventory<MAX_SIZE, GameObjTy, ItemTy>::end()
{
    return m_items.end();
}

template<unsigned int MAX_SIZE, typename GameObjTy, typename ItemTy>
typename Inventory<MAX_SIZE, GameObjTy, ItemTy>::const_iterator Inventory<MAX_SIZE, GameObjTy, ItemTy>::cbegin() const 
{ 
    return m_items.cbegin(); 
}
template<unsigned int MAX_SIZE, typename GameObjTy, typename ItemTy>
typename Inventory<MAX_SIZE, GameObjTy, ItemTy>::const_iterator Inventory<MAX_SIZE, GameObjTy, ItemTy>::cend() const 
{ 
    return m_items.cend(); 
}

template<unsigned int MAX_SIZE, typename GameObjTy, typename ItemTy>
typename Inventory<MAX_SIZE, GameObjTy, ItemTy>::iterator Inventory<MAX_SIZE, GameObjTy, ItemTy>::getItem(std::string_view name)
{
    return m_items.find(name.data());
}

template<unsigned int MAX_SIZE, typename GameObjTy, typename ItemTy>
typename Inventory<MAX_SIZE, GameObjTy, ItemTy>::const_iterator Inventory<MAX_SIZE, GameObjTy, ItemTy>::getItem(std::string_view name) const
{
    return m_items.find(name.data());
}

template<unsigned int MAX_SIZE, typename GameObjTy, typename ItemTy>
void Inventory<MAX_SIZE, GameObjTy, ItemTy>::useItem(std::string_view name, game_object_pointer target)
{
    useItem(getItem(name), target);
}

template<unsigned int MAX_SIZE, typename GameObjTy, typename ItemTy>
template<typename ItemT>
void Inventory<MAX_SIZE, GameObjTy, ItemTy>::addItem(std::string_view id)
{
    if constexpr (!std::is_base_of_v<item_type, ItemT>)
        throw InvalidItemTypeException();

    if (size() >= MAX_SIZE)
    {
        throw InventoryFullException();
    }

    if (m_items.find(id.data()) != m_items.end()) //if we already own this item, increment the count
    {
        if (!m_items[id.data()].first->stackable())
            throw InvalidStackException();
        m_items[id.data()].second += 1; //increment count
        m_size += 1;
    }
    else
    {
        throw InvalidIDException();
    }
}

template<unsigned int MAX_SIZE, typename GameObjTy, typename ItemTy>
template<typename ItemT, typename... Args>
void Inventory<MAX_SIZE, GameObjTy, ItemTy>::emplaceItem(std::string_view id, Args... args)
{
    if constexpr (!std::is_base_of_v<item_type, ItemT>)
        throw InvalidItemTypeException();

    if (size() >= MAX_SIZE)
    {
        throw InventoryFullException();
    }

    m_items[id.data()] = std::make_pair(std::make_unique<ItemT>(std::forward<Args>(args)...), 1);
    m_size += 1;
}

template<unsigned int MAX_SIZE, typename GameObjTy, typename ItemTy>
void Inventory<MAX_SIZE, GameObjTy, ItemTy>::useItem(iterator pos, game_object_pointer target)
{
    if (pos == m_items.end()) throw InvalidItemException();

    //use the item

    ItemDispatcher<GameObjTy> dispatcher { target };

    auto& it = *pos;
    auto& itemPair = it.second;
    auto& item = itemPair.first;

    if (item->equippable())
    {
        dispatcher.setTarget(m_owner);
        if (!item->is_equipped())
        {
            item->set_equip(true);
            item->use(dispatcher);
        }
        else
        {
            item->set_equip(false);
            item->use(dispatcher);
        }
        return;
    }
    else
    {
        dispatcher.setTarget(target);
        item->use(dispatcher); //dispatcher.target == target, see construction above
    }

    if (!item->reusable())
    {
        if (!item->stackable())
            removeItem(pos);
        else
        {
            if (itemPair.second > 1) itemPair.second -= 1; //decrement count if we have more than 1
            else removeItem(pos);
        }
        m_size -= 1;
    }
}

template<unsigned int MAX_SIZE, typename GameObjTy, typename ItemTy>
void Inventory<MAX_SIZE, GameObjTy, ItemTy>::removeItem(iterator pos)
{
    m_items.erase(pos);
}

template<unsigned int MAX_SIZE, typename GameObjTy, typename ItemTy>
void Inventory<MAX_SIZE, GameObjTy, ItemTy>::removeItem(std::string_view name)
{
    removeItem(getItem(name));
}

template<unsigned int MAX_SIZE, typename GameObjTy, typename ItemTy>
void Inventory<MAX_SIZE, GameObjTy, ItemTy>::setOwner(game_object_pointer owner)
{
    m_owner = owner;
}

template<unsigned int MAX_SIZE, typename GameObjTy, typename ItemTy>
typename Inventory<MAX_SIZE, GameObjTy, ItemTy>::game_object_pointer Inventory<MAX_SIZE, GameObjTy, ItemTy>::getOwner() const
{
    return m_owner;
}

template<unsigned int MAX_SIZE, typename GameObjTy, typename ItemTy>
typename Inventory<MAX_SIZE, GameObjTy, ItemTy>::size_type Inventory<MAX_SIZE, GameObjTy, ItemTy>::max_size() const
{
    return MAX_SIZE;
}

template<unsigned int MAX_SIZE, typename GameObjTy, typename ItemTy>
typename Inventory<MAX_SIZE, GameObjTy, ItemTy>::size_type Inventory<MAX_SIZE, GameObjTy, ItemTy>::size() const
{
    return m_size;
}

template<unsigned int MAX_SIZE, typename GameObjTy, typename ItemTy>
template<unsigned int N>
void Inventory<MAX_SIZE, GameObjTy, ItemTy>::merge(Inventory<N, GameObjTy, ItemTy>& other)
{
    if (!merge_fits(other))
        throw InventoryFullException();

    for (auto& it = other.begin(); it != other.end(); std::advance(it, 1))
    {

        this->m_items[it->first] = std::move(it->second);
    }
    other.clear();
}

template<unsigned int MAX_SIZE, typename GameObjTy, typename ItemTy>
void Inventory<MAX_SIZE, GameObjTy, ItemTy>::clear()
{
    m_size = 0;
    m_items.clear();
}

template<unsigned int MAX_SIZE, typename GameObjTy, typename ItemTy>
template<unsigned int N>
bool Inventory<MAX_SIZE, GameObjTy, ItemTy>::merge_fits(Inventory<N, GameObjTy, ItemTy> const& other)
{
    return !(full() || other.size() + this->size() >= max_size());
}

template<unsigned int MAX_SIZE, typename GameObjTy, typename ItemTy>
template<unsigned int N>
void Inventory<MAX_SIZE, GameObjTy, ItemTy>::transfer(Inventory<N, GameObjTy, ItemTy>& destination, std::string_view name)
{   
    if (destination.full())
        return;

    auto& it = getItem(name);
    auto& item = (*it).second;

    destination.contents()[name.data()] = std::move(item);

    m_items.erase(it);
    m_size -= 1;
}

template<unsigned int MAX_SIZE, typename GameObjTy, typename ItemTy>
bool Inventory<MAX_SIZE, GameObjTy, ItemTy>::empty() const
{
    return size() <= 0;
}

template<unsigned int MAX_SIZE, typename GameObjTy, typename ItemTy>
bool Inventory<MAX_SIZE, GameObjTy, ItemTy>::full() const
{
    return max_size() <= size();
}

template<unsigned int MAX_SIZE, typename GameObjTy, typename ItemTy>
unsigned int Inventory<MAX_SIZE, GameObjTy, ItemTy>::getItemCount(std::string_view id) const
{
    if (m_items.find(id.data()) == m_items.end()) throw InvalidItemException();
    return m_items.at(id.data()).second;
}

I will also add a main.cpp test file just to demonstrate how this can be used

main.cpp

#include "DamagePotion.h"
#include "Inventory.h"
#include "HealPotion.h"
#include "Sword.h"
#include "ItemUtil.h"

std::ostream& operator<<(std::ostream& out, ItemID const& id)
{
    if (id == ItemID::DAMAGE_POTION)
    {
        out << "ID_DAMAGE_POTION";
    }
    else if (id == ItemID::DEFAULT_ITEM) out << "ID_DEFAULT_ITEM";
    else if (id == ItemID::HEAL_POTION) out << "ID_HEAL_POTION";
    else if (id == ItemID::SWORD) out << "ID_SWORD";
    return out;
}

//Replace temp::GameObject class with the GameObject class used by your game

class Player : public temp::GameObject
{
public:
    Player() : temp::GameObject(200)
    {
        try
        {
            m_inventory.emplaceItem<DamagePotion>("Damage Potion I", 50);
            m_inventory.emplaceItem<HealPotion>("Heal Potion I", 70);
            m_inventory.emplaceItem<Sword>("Sword I", 20);

            std::cout << "Inventory contents after adding base items:n";
            for (auto const& it : m_inventory.contents()) std::cout << it.second.first->id() << "n";
            std::cout << "n";

            m_inventory.useItem("Damage Potion I", this);
            m_inventory.useItem("Heal Potion I", this);
            m_inventory.useItem("Sword I");

            std::cout << "Inventory contents after using base items:n";
            for (auto const& it : m_inventory.contents()) std::cout << it.second.first->id() << "n";
            std::cout << "n";

            m_inventory.useItem("Sword I"); // will unequip Sword I

            std::cout << "Inventory contents after unequipping Sword I:n";
            for (auto const& it : m_inventory.contents()) std::cout << it.second.first->id() << "n";
            std::cout << "n";

            chest.emplaceItem<DamagePotion>("CDmgPot", 100);
            chest.emplaceItem<HealPotion>("CHealPot", 200);

            std::cout << "Chest contents after adding base items:n";
            for (auto const& it : chest.contents()) std::cout << it.second.first->id() << "n";
            std::cout << "n";

            m_inventory.merge(chest);

            std::cout << "Chest contents after merging with inventory:n";
            for (auto const& it : chest.contents()) std::cout << it.second.first->id() << "n";
            std::cout << "n";
            std::cout << "Inventory contents after merging with chest:n";
            for (auto const& it : m_inventory.contents()) std::cout << it.second.first->id() << "n";
            std::cout << "n";

            chest.emplaceItem<Sword>("CSword", 50);

            std::cout << "Chest contents after adding CSword:n";
            for (auto const& it : chest.contents()) std::cout << it.second.first ->id() << "n";
            std::cout << "n";

            chest.transfer(m_inventory, "CSword");

            std::cout << "Inventory contents after transferring CSword from chest:n";
            for (auto const& it : m_inventory.contents()) std::cout << it.second.first->id() << "n";
            std::cout << "n";

            chest.emplaceItem<Sword>("CSword", 20);

            std::cout << "Chest contents after adding a CSword:n";
            for (auto const& it : chest.contents()) std::cout << it.second.first->id() << "n";
            std::cout << "n";

            chest.removeItem("CSword");

            std::cout << "Chest contents after removing a CSword:n";
            for (auto const& it :chest.contents()) std::cout << it.second.first->id() << "n";
            std::cout << "n";
        }
        catch (std::runtime_error e)
        {
            std::cerr << e.what();
        }
    }

private:
    Inventory<200> m_inventory { this };

    Inventory<5> chest { this };
};


struct InvalidItem
{

};

struct InvalidEquippable
{
    void equippable()
    {

    }
};

class TestClass : public temp::GameObject
{
public:
    TestClass() : temp::GameObject(50)
    {
        try
        {

            Inventory<100> inv { this };

            inv.emplaceItem<ItemType<ItemID::DAMAGE_POTION>::type>("P", 20);
            inv.addItem<DamagePotion>("P");

            std::cout << ItemName(ItemID::HEAL_POTION) << ' ' << ItemId<Sword>() << 'n';

            std::cout << inv.getItemCount("P") << 'n';

            inv.useItem("P", this);
            inv.useItem("P", this);

            std::cout << getHealth() << 'n';

            std::cout << inv.getItemCount("P") << 'n';
        }
        catch (Inventory<100>::Exception e)
        {
            std::cout << e.what() << "n";
        }
    }
};



int main()
{

    Player p;


//  IItem* base_ptr;

//  Inventory<200> {nullptr};

    //Fails to compile due to traits
//  Inventory<100, temp::GameObject, InvalidItem> {nullptr};
//  Inventory<100, temp::GameObject, InvalidEquippable> {nullptr};

//  base_ptr = new DamagePotion(20);

//  temp::GameObject target { 100 };
/*
    std::cout << "Using Item: n";
    std::cout << "Name:t" << base_ptr->name() << "n";
    std::cout << "ID:t" << base_ptr->id() << "n";
    base_ptr->use(&target);
*/


    std::cin.get();
}

Note: I have just read a meta post that says that it is not allowed to post GitHub links. I am not sure what I should do in this case, as I mostly want the Inventory header reviewed. The files in the github repo are only for when there really is more information required.

EDIT: as per request in the comments, here are some further explanations:

Architecture

As I created this project with portability across other projects in mind, so as a kind of library, the architecture is designed to be as simple to use as possible. All Items will be inherited from a base IItem class, or any other class that supports the functions in the Inventory::Traits class. The inventory is responsible for managing the resources of every item, and for creating them. So when adding an item you call

inventory.emplaceItem<SomePotion>("Potion", 50); //the 50 is passed to SomePotion's constructor

MAX_SIZE

MAX_SIZE is a template parameter due to an unlucky design choice at the very beginning. Since I did not want to do lots of allocations for items, I wanted to have the size fixed. This is probably completely unneccesary, but annoying to remove now.

Item usage

Since the Inventory is responsible for using the items (by calling useItem("name", target_ptr);), I needed some way of doing that. I chose for the Visitor Pattern, which I implemented with the ItemDispatcher class.

Data Structure

The items are stored in a std::unordered_map<std::string, std::pair<std::unique_ptr<Item>, unsigned>>; I will break down why I chose for this. First of all, I wanted the items to be accessible in some way. Iterators were clumsy, because they would invalidate when an item is added to the inventory. I still kept the begin() and end() functions for if you would want to iterate through an inventory in a loop. So I chose for an undoredered map, with string indices so you can give every item its own string ID. The items are stored as a pair of [Item, Count], since I wanted to be able to stack items where possible (Item.stackable() == true).

Reusability

About the reusable in multiple projects. With that I mean that if I, at some point decide to make an RPG game, and the character needs an inventory, that I can just use this exact Inventory class without any modifications. If I then later want to make another, completely different game that also features an Inventory, or some other way of storing items, I can reuse this again.

Techniques

The Traits class is used as a wrapper for the SFINAE type traits that I use to detect if an eventual custom Item type supports all required functions, set for example like:

Inventory<200, MyCoolGameObject, MyCoolItem> inventory;

Traits is a class because I can’t do

class X
{
    namespace Y
    {

    };
};


Get this bounty!!!

#StackBounty: #game #health #accessibility #fitness #wii Wii games for a child with motor impairment

Bounty: 50

I have a 4-year old child with motor impairment, and recently came across several papers regarding how playing Wii games can yield improvements in dexterity, limb coordination, balance and motor proficiency, such as [1] [2].

What I’m looking for is recommendations for games that would be suitable. Requirements:

  1. Ideally, to begin with at least, the games should be playable using only movement of the controller (without the buttons)
  2. I’m also interested in games that use only the ‘nunjuks’
  3. Games must not be too fast paced

As an example, the Wii Sports: Tennis game fits the first requirement, but it is too fast paced.


Get this bounty!!!

#StackBounty: #c #game #ai Solving Chain Reaction on Small Boards: Verifying Correctness

Bounty: 200

I’m writing a program that finds the minimax value of Chain Reaction on various small boards (à la Solving Go on Small Boards). Here’s a brief description of the game of Chain Reaction:

Chain Reaction is a two player combinatorial game played on an n×m game board (where n,m &geq; 2) in which players Red and Green take turns to place a single atom of their color in an empty square or in a square that they control. Placing more than one atom in the same square creates a molecule.

The more atoms in a molecule, the more unstable it becomes. When a molecule becomes too unstable it splits and its atoms move to orthogonally adjacent squares, changing the color of the atoms in those squares to match their color. This can cause a chain reaction of splitting molecules.

Molecules in the four corner squares require two atoms to split. Molecules on the edge of the board require three atoms to split. Molecules in the center of the board require four atoms to split. Atoms of a splitting molecule each move in a different direction. The game ends immediately after one player captures all of the atoms belonging to the other player.

I found the minimax value of Chain Reaction on the 2×2, 2×3, 2×4, 2×5, 2×6, 3×3 and 3×4 boards. However, I haven’t verified their correctness. Here are the values I obtained:

+---+---+---+---+---+---+
|nm| 2 | 3 | 4 | 5 | 6 |
+---+---+---+---+---+---+
| 2 |-2 |+4 |-6 |+8 |+10|
| 3 |   |+7 |+10|   |   |
+---+---+---+---+---+---+

A positive value represents a win for Red. A negative value represents a loss for Red. The absolute value represents the number of moves to the terminal state under optimal play. A move consists of a turn by each player. Therefore, a value of -2 means that Red loses in two moves (i.e. Red loses in 4 turns). On the other hand, a value of +4 means that Red wins in four moves (i.e. Red wins in 7 turns). This is assuming Red plays first starting on an empty board.

Anyway, here’s the C program that I wrote to obtain these values:

#include <stdio.h>
#include <stdlib.h>

typedef struct {
    int x, y, z, m, v, h;
} transposition;

transposition table[2][0x1000000] = {0};

int v, w;

int negamax(int x, int y, int z, int m, int alpha, int beta) {
    static int result = -8000, color = 1, height;

    if (m) { // a move is to be played
        // begin: add the atom to the board
        int x2 = y & z, y2 = w ^ y ^ z ^ w & x2, z2 = ~z & (x | y), m2 = ~m;

        x  = x2 & m | m2 & x;
        y2 = y2 & m | m2 & y;
        z2 = z2 & m | m2 & z;
        m &= z & (w | y);

        y = y2, z = z2;
        // end: add the atom to the board

        while (m) { // a chain reaction is caused
            // move the atoms to the North, East, west and South squares
            int news[4] = { m >> v, m << 1, m >> 1, m << v };

            m = 0; // splitting molecules for next iteration

            for (int i = 0; i < 4; i++) { // for each direction, add atoms
                int n = news[i];

                x2 = y & z, y2 = w ^ y ^ z ^ w & x2, z = ~z & (x | y), m2 = ~n;

                x  = x2 & n | m2 & x;
                y2 = y2 & n | m2 & y;
                z2 = z2 & n | m2 & z;
                m |= n & z & (w | y); // collect splitting molecules

                y = y2, z = z2;
            }

            if ((x & (y | z)) == 0) { // the chain reaction wiped out the player
                height = 0;
                return result;
            }
        }

        x ^= y | z; // swap players (i.e. Red becomes Green and vice versa)
    }

    // begin: calculate hash index and lookup transposition tables
    int index = w;
    index ^= x + 0x9E3779B9 + (index << 6) + (index >> 2);
    index ^= y + 0x9E3779B9 + (index << 6) + (index >> 2);
    index ^= z + 0x9E3779B9 + (index << 6) + (index >> 2);
    index &= 0xFFFFFF;

    for (int i = 0; i < 2; i++) {
        transposition t = table[i][index];

        if (t.x == x && t.y == y && t.z == z) {
            height = t.h;

            switch (t.v & 3) {
            case 0: return t.v;
            case 1: if (t.v > alpha) alpha = t.v; break;
            case 2: if (t.v < beta)  beta  = t.v; break;
            }

            if (alpha >= beta) return t.v;

            break;
        }
    } // end: calculate hash index and lookup transposition tables

    int moves = y | z, cut = beta + 1 & ~3, value = -9000, h = 0, move;

    result += 2 * (1 + color); // update return value for next ply
    color =- color; // switch players

    for (moves = (w | x | moves) & ~(x & moves); moves; moves &= moves - 1) {
        m = moves & -moves; // the move is played when we call negamax

        int val = -negamax(x, y, z, m, -beta, -alpha);

        if (val > value) { value = val; move = m; }
        if (val > alpha) alpha = val;
        if (++height > h) h = height;
        if (alpha >= beta) { value = (value + 1 & ~3) + 1; break; }
    }

    color =- color; // switch back players
    result -= 2 * (1 + color); // restore return value for current ply

    // begin: save return value in transposition table
    transposition t = table[0][index];

    int i = h < t.h;

    if (!i) table[1][index] = t;

    t.x = x;
    t.y = y;
    t.z = z;
    t.m = move;
    t.v = value;
    t.h = height = h;

    table[i][index] = t;
    // end: save return value in transposition table

    return value;
}

int main(int argc, char * argv[]) {
    if (argc != 3) {
        printf("Usage: %s nrows mcolsn", argv[0]);
        printf("    nrows: number of rows (in the interval [2, 5])n");
        printf("    mcols: number of cols (in the interval [n, 33/n-1])n");
        return EXIT_SUCCESS;
    }

    int n = atoi(argv[1]);

    if (n < 2 || n > 5) {
        printf("Error: nrows must be in the interval [2, 5]n");
        return EXIT_FAILURE;
    }

    int m = atoi(argv[2]);

    if (m < n || m > 33 / n - 1) {
        printf("Error: mcols must be in the interval [n, 33/n-1]");
        return EXIT_FAILURE;
    }

    // begin: calculate initial empty bitboard values
    v = m + 1, w = (1 << m) - 1;

    {
        int a = (1 << m - 1) + 1, b = w - a;
        int i = n - 1, j = v, k = i * j;

        w |= w << k;
        x = a | a << k;

        for (int k = 1; k < i; k++, j += v) {
            w |= a << j;
            x |= b << j;
        }
    }
    // end: calculate initial empty bitboard values

    int value = negamax(x, 0, 0, 0, -8000, 8000) / 4;

    printf("#%dn", (value < 0 ? -2000 : 2000) - value);

    return EXIT_SUCCESS;
}

Things you should know about my implementation:

  • I used negamax with alpha beta pruning and transposition tables.
  • I used the two-deep replacement scheme for transposition tables (with 2×224 entries).
  • I used bitboards to represent the game state. The variables w, x, y and z hold the four bitboards that represent the game state. The variable w never changes and hence it’s a global variable. The variable m holds the bitboard for the current move. An explanation of how these bitboards work is found here.
  • The values are represented by integers such that:
    • +2000 represents a win in 0 moves.
    • +1999 represents a win in 1 move.
    • +1998 represents a win in 2 moves.
    • -1998 represents a loss in 2 moves.
    • -1999 represents a loss in 1 move.
    • -2000 represents a loss in 0 moves.
  • I’m using integrated bounds and values. Hence, the values are multiplied by 4. For lower bounds, the values are incremented. For upper bounds, the values are decremented.

I’d really appreciate it if you’d verify the correctness of my program and the values that it generates. Critiques and suggestions to improve my code are also welcome.


Get this bounty!!!

#StackBounty: #java #algorithm #game #collision Handling collision on a turn-based 2D game

Bounty: 100

I have implemented a collision check algorithm that I have created for my game, and the algorithm uses recursion.

Now it all works fine, and I thought to myself, what if there was a way to solve this algorithm, without using recursion, and more something with promise callbacks, or something, but unfortunately after researching, I could not find any way that I can think of at this time, so I thought I should ask there.

Now by turn-based I mean that the game is basically a big board of rectangles where each rectangle is a tile position, and there are ships, each ship can be on a tile unless its a rock, or another ship on there.

Now the collision is handled server-sided, so the coordinates are always integers, so it’s not some graphical collision check, don’t get me wrong.

There’s an Example of collision, you can see that the ship selected the moves Right, Left, Right, and the left move collided, because the other ship does not move at that turn of the move.

So how does my algorithm work

    // Loop through all turns
    for (int turn = 0; turn < 4; turn++) {
        // Loop through phases in the turn (split turn into phases, e.g turn left is 2 phases, turn forward is one phase).
        // So if stopped in phase 1, and its a turn left, it will basically stay in same position, if in phase 2, it will only
        // move one step instead of 2 full steps.
        for (int phase = 0; phase < 2; phase++) {

             // Go through all players and check if their move causes a collision
             for (Player p : players) {

                 // If a player is already collided in this step, we don't want him to move
                 // anywhere, so we skip this iteration
                 if (p.getCollisionStorage().isCollided(turn)) {
                     continue;
                 }

                 // Checks collision for the player, according to his current step #phase index and turn
                 collision.checkCollision(p, turn, phase, true);
           }
        }
     }

So basically checkCollision(Player p, int turn, int phase, boolean setPos) is where the collision is handled, and is a big method. This method basically goes through the collision rules and saves into players’ collision storage if it collided or not.

But here comes the recursion part: When a tile is already claimed, in the method, we check if the claimed player has a move placed on that turn, if yes, we will run the checkCollision method on that player that claimed it, if the player collided during his move, the method will return true, and then we know that this claimed player did not move, so the player we check for will collide with him. And now imagine a line of ships, and ship A wants to move forward, so we check ship B, ship B checks ship C and on and on and makes sure that every checkCollision returned false, so ship A can move and rest can move as-well.

Now I am curious, if there is a better way on implementing this besides using a recursion?

/**
 * Checks if a player has a collision according to his move, in the given turn and move-phase
 * @param p             The player to check
 * @param turn          The turn
 * @param phase         The move-phase step
 * @param setPosition   If to set the next position or not on non-collided result
 * @return  <code>TRUE</code> If the player was collided, <code>FALSE</code> if not.
 */
public boolean checkCollision(Player p, int turn, int phase, boolean setPosition) {
    // The current selected move of the player
    MoveType move =  p.getMoves().getMove(turn);

    // If this player was bumped, and a move was not selected, we want to process the bump animation
    // But we have to check if the position to be bumped is available to be claimed
    if (move == MoveType.NONE && p.getCollisionStorage().isBumped()) {
        Position pos = p.getCollisionStorage().getBumpAnimation().getPositionForAnimation(p);
        Player claimed = players.getPlayerByPosition(pos.getX(), pos.getY());
        // Claiming checking for the new position for bump
        return claimed != null && (claimed.getMoves().getMove(turn) == MoveType.NONE || checkCollision(claimed, turn, phase, false));
    }

    // Use the current position as default, imply we have already set it
    Position position = p;

    // If not set by default.txt on previous loops, gets the next position on the map for the given phase on the given move
    if (!p.getCollisionStorage().isPositionChanged()) {
        position = move.getNextPositionWithPhase(p, p.getFace(), phase);
    }
    // If the player has moved since his last position
    if (!position.equals(p)) {
        // Check for bounds collision with the border
        if (checkBoundCollision(p, turn, phase) || checkRockCollision(p, turn, phase)) {
            return true;
        }
        // Check if the next position is claimed by another player, null result if not
        Player claimed = players.getPlayerByPosition(position.getX(), position.getY());

        // If the result is not null, the position is claimed
        if (claimed != null) {
            Position claimedNextPos = claimed;
            if (!claimed.getCollisionStorage().isPositionChanged()) {
                claimedNextPos = claimed.getMoves().getMove(turn).getNextPositionWithPhase(claimed, claimed.getFace(), phase);
            }

            // Check if the claimed position doesn't move away
            if (claimed.getMoves().getMove(turn) == MoveType.NONE || claimedNextPos.equals(claimed)) {
                if (move != MoveType.FORWARD || claimed.getVessel().getSize() >= p.getVessel().getSize()) {
                    collide(p, claimed, turn, phase);
                }

                if (move == MoveType.FORWARD && canBumpPlayer(p, claimed, turn, phase)) {
                    bumpPlayer(claimed, p, turn, phase);
                    p.set(position);
                    p.getCollisionStorage().setPositionChanged(true);
                }

                claimed.getVessel().appendDamage(p.getVessel().getRamDamage());

                return true;
            }
            else if (claimedNextPos.equals(p)) { // If they switched positions (e.g nose to nose, F, F move)
                collide(p, claimed, turn, phase);
                collide(claimed, p, turn, phase);
                return true;
            }
            else {
                // Make sure that the claimed position moves away successfully
                if (!checkCollision(claimed, turn, phase, false)) {
                    if (setPosition) {
                        // Moved successfully, claim position
                        p.set(position);
                        p.getCollisionStorage().setPositionChanged(true);
                    }
                }
                else {
                    // did not move successfully, collide
                    collide(p, claimed, turn, phase);
                    collide(claimed, p, turn, phase);
                    return true;
                }
            }
        } else {
            // List of players that collided with this player, while performing this move, in this phase
            List<Player> collisions = getPlayersTryingToClaim(p, position, turn, phase);

            if (collisions.size() > 0) { // Collision has happened
                collisions.add(p);

                Player largest = getLargestSize(collisions);

                if (countPlayersForSize(collisions, largest.getVessel().getSize()) > 1) {
                    // Stop players from movement
                    for (Player pl : collisions) {
                        pl.getCollisionStorage().setCollided(turn, phase);
                        collide(pl, pl, turn, phase);
                    }
                }
                else {
                    for (Player pl : collisions) {
                        if (pl == largest) {
                            continue;
                        }
                        pl.getCollisionStorage().setCollided(turn, phase);
                        collide(pl, largest, turn, phase);
                    }
                    if (!largest.getCollisionStorage().isPositionChanged()) {
                        largest.set(position);
                        largest.getCollisionStorage().setPositionChanged(true);
                    }
                }
                return true;
            } else {
                if (setPosition) {
                    p.set(position);
                    p.getCollisionStorage().setPositionChanged(true);
                }
            }
        }
    }

    return false;
}


Get this bounty!!!

#StackBounty: #code-challenge #game #cellular-automata #game-of-life #tetris Build a working game of Tetris in Conway's Game of Life

Bounty: 100

Here is a theoretical question – one that doesn’t afford an easy answer in any case, not even the trivial one.

In Conway’s Game of Life, there exist constructs such as the metapixel which allow the Game of Life to simulate any other Game-of-Life rule system as well. In addition, it is known that the Game of Life is Turing-complete.

Your task is to build a cellular automation using the rules of Conway’s game of life that will allow for the playing of a game of Tetris.

Your program will receive input by manually changing the state of the automaton at a specific generation to represent an interrupt (e.g. moving a piece left or right, dropping it, rotating it, or randomly generating a new piece to place onto the grid), counting a specific number of generations as waiting time, and displaying the result somewhere on the automation. The displayed result must visibly resemble an actual Tetris grid.

Your program will be scored on the following things, in order (with lower criteria acting as tiebreakers for higher criteria):

  • Bounding box size — the rectangular box with the smallest area that completely contains the given solution wins.

  • Smaller changes to input — the fewest cells (for the worst case in your automaton) that need to be manually adjusted for an interrupt wins.

  • Fastest execution — the fewest generations to advance one tick in the simulation wins.

  • Initial live cell count — smaller count wins.

  • First to post — earlier post wins.


Get this bounty!!!